From 583deca3df8738602c2c7d701836ef9c25b897b3 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Tue, 21 Apr 2020 16:16:48 +0300 Subject: [PATCH 01/98] Initial experiments with nuclio --- components/dextr/nuclio/Dockerfile | 41 +++++++ components/dextr/nuclio/deploy.sh | 3 + components/dextr/nuclio/function.yaml | 20 ++++ components/dextr/nuclio/function/dextr.py | 101 ++++++++++++++++++ .../dextr/nuclio/function/inference_engine.py | 53 +++++++++ components/dextr/nuclio/function/main.py | 26 +++++ components/dextr/nuclio/function/python3 | 6 ++ .../dextr/nuclio/function/requirements.txt | 1 + components/dextr/nuclio/input.json | 4 + components/dextr/nuclio/run.sh | 3 + 10 files changed, 258 insertions(+) create mode 100644 components/dextr/nuclio/Dockerfile create mode 100755 components/dextr/nuclio/deploy.sh create mode 100644 components/dextr/nuclio/function.yaml create mode 100644 components/dextr/nuclio/function/dextr.py create mode 100644 components/dextr/nuclio/function/inference_engine.py create mode 100644 components/dextr/nuclio/function/main.py create mode 100755 components/dextr/nuclio/function/python3 create mode 100644 components/dextr/nuclio/function/requirements.txt create mode 100644 components/dextr/nuclio/input.json create mode 100755 components/dextr/nuclio/run.sh diff --git a/components/dextr/nuclio/Dockerfile b/components/dextr/nuclio/Dockerfile new file mode 100644 index 000000000000..bf9d52a1c875 --- /dev/null +++ b/components/dextr/nuclio/Dockerfile @@ -0,0 +1,41 @@ +# https://github.com/nuclio/nuclio/blob/master/docs/reference/runtimes/python/python-reference.md#python-reference +ARG NUCLIO_LABEL=0.7.1 +ARG NUCLIO_ARCH=amd64 +ARG NUCLIO_BASE_IMAGE=openvino/ubuntu18_runtime:2020.2 +ARG NUCLIO_ONBUILD_IMAGE=nuclio/handler-builder-python-onbuild:${NUCLIO_LABEL}-${NUCLIO_ARCH} + +# Supplies processor uhttpc, used for healthcheck +FROM nuclio/uhttpc:latest-amd64 as uhttpc + +# Supplies processor binary, wrapper +FROM ${NUCLIO_ONBUILD_IMAGE} as processor + +# From the base image +FROM ${NUCLIO_BASE_IMAGE} + +# Copy required objects from the suppliers +COPY --from=processor /home/nuclio/bin/processor /usr/local/bin/processor +COPY --from=processor /home/nuclio/bin/py /opt/nuclio/ +COPY --from=uhttpc /home/nuclio/bin/uhttpc /usr/local/bin/uhttpc + +RUN pip3 install nuclio-sdk msgpack + +# Readiness probe +HEALTHCHECK --interval=1s --timeout=3s CMD /usr/local/bin/uhttpc --url http://127.0.0.1:8082/ready || exit 1 + +# USER CONTENT +USER root +WORKDIR /opt/nuclio +ENV PATH=/opt/nuclio:${PATH} +RUN curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip +RUN unzip dextr_model_v1.zip +COPY ./function/requirements.txt ./ +RUN pip3 install -r requirements.txt +COPY ./function ./ +COPY ./function.yaml /etc/nuclio/config/processor/processor.yaml + +USER openvino +# END OF USER CONTENT + +# Run processor with configuration and platform configuration +CMD [ "processor" ] diff --git a/components/dextr/nuclio/deploy.sh b/components/dextr/nuclio/deploy.sh new file mode 100755 index 000000000000..0f58b8a4d9f3 --- /dev/null +++ b/components/dextr/nuclio/deploy.sh @@ -0,0 +1,3 @@ +docker build -t cvat/dextr:latest . +#nuctl deploy -f ./function.yaml +nuctl deploy dextr --run-image cvat/dextr:latest --runtime "python:3.6" --handler "main:handler" --platform local diff --git a/components/dextr/nuclio/function.yaml b/components/dextr/nuclio/function.yaml new file mode 100644 index 000000000000..41db45ba5a38 --- /dev/null +++ b/components/dextr/nuclio/function.yaml @@ -0,0 +1,20 @@ +apiVersion: "nuclio.io/v1" +kind: "NuclioFunction" +metadata: + name: dextr + namespace: cvat + +spec: + description: Deep Extreme Cut + runtime: "python:3.6" + handler: main:handler + build: + image: cvat/dextr:latest + mode: "neverBuild" + eventTimeout: 30 + replicas: 0 + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 diff --git a/components/dextr/nuclio/function/dextr.py b/components/dextr/nuclio/function/dextr.py new file mode 100644 index 000000000000..cc6ab65a3286 --- /dev/null +++ b/components/dextr/nuclio/function/dextr.py @@ -0,0 +1,101 @@ +# Copyright (C) 2018-2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from inference_engine import make_plugin_or_core, make_network + +import os +import cv2 +import numpy as np + +_IE_CPU_EXTENSION = os.getenv("IE_CPU_EXTENSION", "libcpu_extension_avx2.so") +_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) + +_DEXTR_PADDING = 50 +_DEXTR_TRESHOLD = 0.9 +_DEXTR_SIZE = 512 + +class DEXTR_HANDLER: + def __init__(self): + self._plugin = None + self._network = None + self._exec_network = None + self._input_blob = None + self._output_blob = None + self._plugin = make_plugin_or_core() + self._network = make_network('dextr.xml', 'dextr.bin') + self._input_blob = next(iter(self._network.inputs)) + self._output_blob = next(iter(self._network.outputs)) + if getattr(self._plugin, 'load_network', False): + self._exec_network = self._plugin.load_network(self._network, 'CPU') + else: + self._exec_network = self._plugin.load(network=self._network) + + # Input: + # image: PIL image + # points: [[x1,y1], [x2,y2], [x3,y3], [x4,y4], ...] + # Output: + # polygon: [[x1,y1], [x2,y2], [x3,y3], [x4,y4], ...] + def handle(self, image, points): + numpy_image = np.array(image) + points = np.asarray(points, dtype=int) + bounding_box = ( + max(min(points[:, 0]) - _DEXTR_PADDING, 0), + max(min(points[:, 1]) - _DEXTR_PADDING, 0), + min(max(points[:, 0]) + _DEXTR_PADDING, numpy_image.shape[1] - 1), + min(max(points[:, 1]) + _DEXTR_PADDING, numpy_image.shape[0] - 1) + ) + + # Prepare an image + numpy_cropped = np.array(image.crop(bounding_box)) + resized = cv2.resize(numpy_cropped, (_DEXTR_SIZE, _DEXTR_SIZE), + interpolation = cv2.INTER_CUBIC).astype(np.float32) + + # Make a heatmap + points = points - [min(points[:, 0]), min(points[:, 1])] + [_DEXTR_PADDING, _DEXTR_PADDING] + points = (points * [_DEXTR_SIZE / numpy_cropped.shape[1], _DEXTR_SIZE / numpy_cropped.shape[0]]).astype(int) + heatmap = np.zeros(shape=resized.shape[:2], dtype=np.float64) + for point in points: + gaussian_x_axis = np.arange(0, _DEXTR_SIZE, 1, float) - point[0] + gaussian_y_axis = np.arange(0, _DEXTR_SIZE, 1, float)[:, np.newaxis] - point[1] + gaussian = np.exp(-4 * np.log(2) * ((gaussian_x_axis ** 2 + gaussian_y_axis ** 2) / 100)).astype(np.float64) + heatmap = np.maximum(heatmap, gaussian) + cv2.normalize(heatmap, heatmap, 0, 255, cv2.NORM_MINMAX) + + # Concat an image and a heatmap + input_dextr = np.concatenate((resized, heatmap[:, :, np.newaxis].astype(resized.dtype)), axis=2) + input_dextr = input_dextr.transpose((2,0,1)) + + pred = self._exec_network.infer(inputs={self._input_blob: input_dextr[np.newaxis, ...]})[self._output_blob][0, 0, :, :] + pred = cv2.resize(pred, tuple(reversed(numpy_cropped.shape[:2])), interpolation = cv2.INTER_CUBIC) + result = np.zeros(numpy_image.shape[:2]) + result[bounding_box[1]:bounding_box[1] + pred.shape[0], bounding_box[0]:bounding_box[0] + pred.shape[1]] = pred > _DEXTR_TRESHOLD + + # Convert a mask to a polygon + result = np.array(result, dtype=np.uint8) + cv2.normalize(result,result,0,255,cv2.NORM_MINMAX) + contours = None + if int(cv2.__version__.split('.')[0]) > 3: + contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[0] + else: + contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[1] + + contours = max(contours, key=lambda arr: arr.size) + if contours.shape.count(1): + contours = np.squeeze(contours) + if contours.size < 3 * 2: + raise Exception('Less then three point have been detected. Can not build a polygon.') + + result = [] + for point in contours: + result.append([int(point[0]), int(point[1])]) + + return result + + def __del__(self): + if self._exec_network: + del self._exec_network + if self._network: + del self._network + if self._plugin: + del self._plugin diff --git a/components/dextr/nuclio/function/inference_engine.py b/components/dextr/nuclio/function/inference_engine.py new file mode 100644 index 000000000000..fb6b543d34e2 --- /dev/null +++ b/components/dextr/nuclio/function/inference_engine.py @@ -0,0 +1,53 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version + +import subprocess +import os +import platform + +_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) + + +def _check_instruction(instruction): + return instruction == str.strip( + subprocess.check_output( + 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True + ).decode('utf-8') + ) + + +def make_plugin_or_core(): + version = get_version() + use_core_openvino = False + try: + major, minor, reference = [int(x) for x in version.split('.')] + if major >= 2 and minor >= 1 and reference >= 37988: + use_core_openvino = True + except Exception: + pass + + if use_core_openvino: + ie = IECore() + return ie + + if _IE_PLUGINS_PATH is None: + raise OSError('Inference engine plugin path env not found in the system.') + + plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH]) + if (_check_instruction('avx2')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so')) + elif (_check_instruction('sse4')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so')) + elif platform.system() == 'Darwin': + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension.dylib')) + else: + raise Exception('Inference engine requires a support of avx2 or sse4.') + + return plugin + + +def make_network(model, weights): + return IENetwork(model = model, weights = weights) diff --git a/components/dextr/nuclio/function/main.py b/components/dextr/nuclio/function/main.py new file mode 100644 index 000000000000..895c4b3bfbd7 --- /dev/null +++ b/components/dextr/nuclio/function/main.py @@ -0,0 +1,26 @@ +import json +import base64 +from PIL import Image +import io +import dextr +import sys +import time + +def init_context(context): + context.logger.info("Init context... 0%") + dextr_handler = dextr.DEXTR_HANDLER() + setattr(context.user_data, 'dextr_handler', dextr_handler) + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("call handler") + data = event.body + points = data["points"] + buf = io.BytesIO(base64.b64decode(data["image"])) + image = Image.open(buf) + + polygon = context.user_data.dextr_handler.handle(image, points) + return context.Response(body=json.dumps(polygon), + headers={}, + content_type='application/json', + status_code=200) diff --git a/components/dextr/nuclio/function/python3 b/components/dextr/nuclio/function/python3 new file mode 100755 index 000000000000..b80f584712d7 --- /dev/null +++ b/components/dextr/nuclio/function/python3 @@ -0,0 +1,6 @@ +#!/bin/bash + +args=$@ + +. /opt/intel/openvino/bin/setupvars.sh +/usr/bin/python3 $args diff --git a/components/dextr/nuclio/function/requirements.txt b/components/dextr/nuclio/function/requirements.txt new file mode 100644 index 000000000000..5873a2224bf9 --- /dev/null +++ b/components/dextr/nuclio/function/requirements.txt @@ -0,0 +1 @@ +Pillow \ No newline at end of file diff --git a/components/dextr/nuclio/input.json b/components/dextr/nuclio/input.json new file mode 100644 index 000000000000..51910035dd7c --- /dev/null +++ b/components/dextr/nuclio/input.json @@ -0,0 +1,4 @@ +{ + "points": [[1,1], [100,1], [100,100], [1,100]], + "image": "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" +} diff --git a/components/dextr/nuclio/run.sh b/components/dextr/nuclio/run.sh new file mode 100755 index 000000000000..c9e40575275b --- /dev/null +++ b/components/dextr/nuclio/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +http POST http://localhost:50953 < ./input.json From f9c58e68b9423dd796025622f394dd993425b906 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Wed, 22 Apr 2020 23:34:51 +0300 Subject: [PATCH 02/98] Update nuclio prototype --- components/dextr/nuclio/deploy.sh | 4 +-- .../dextr/nuclio/{function => }/dextr.py | 0 components/dextr/nuclio/function.yaml | 35 ++++++++++++++++--- .../nuclio/{function => }/inference_engine.py | 0 .../dextr/nuclio/{function => }/main.py | 0 components/dextr/nuclio/pip | 4 +++ .../dextr/nuclio/{function => }/python3 | 0 .../nuclio/{function => }/requirements.txt | 0 components/dextr/nuclio/run.sh | 2 +- 9 files changed, 38 insertions(+), 7 deletions(-) rename components/dextr/nuclio/{function => }/dextr.py (100%) rename components/dextr/nuclio/{function => }/inference_engine.py (100%) rename components/dextr/nuclio/{function => }/main.py (100%) create mode 100755 components/dextr/nuclio/pip rename components/dextr/nuclio/{function => }/python3 (100%) rename components/dextr/nuclio/{function => }/requirements.txt (100%) diff --git a/components/dextr/nuclio/deploy.sh b/components/dextr/nuclio/deploy.sh index 0f58b8a4d9f3..aaf34d78aeaa 100755 --- a/components/dextr/nuclio/deploy.sh +++ b/components/dextr/nuclio/deploy.sh @@ -1,3 +1,3 @@ docker build -t cvat/dextr:latest . -#nuctl deploy -f ./function.yaml -nuctl deploy dextr --run-image cvat/dextr:latest --runtime "python:3.6" --handler "main:handler" --platform local +nuctl deploy -f ./function.yaml +#nuctl deploy dextr --run-image cvat/dextr:latest --runtime "python:3.6" --handler "main:handler" --platform local diff --git a/components/dextr/nuclio/function/dextr.py b/components/dextr/nuclio/dextr.py similarity index 100% rename from components/dextr/nuclio/function/dextr.py rename to components/dextr/nuclio/dextr.py diff --git a/components/dextr/nuclio/function.yaml b/components/dextr/nuclio/function.yaml index 41db45ba5a38..20418676562d 100644 --- a/components/dextr/nuclio/function.yaml +++ b/components/dextr/nuclio/function.yaml @@ -9,10 +9,37 @@ spec: runtime: "python:3.6" handler: main:handler build: - image: cvat/dextr:latest - mode: "neverBuild" - eventTimeout: 30 - replicas: 0 + image: cvat/dextr + baseImage: openvino/ubuntu18_runtime:2020.2 + + directives: + preCopy: + - kind: USER + value: root + + - kind: WORKDIR + value: /opt/nuclio + + - kind: ENV + value: PATH=/opt/nuclio:${PATH} + + postCopy: + - kind: RUN + value: curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip + - kind: RUN + value: unzip dextr_model_v1.zip + - kind: RUN + value: pip3 install -r requirements.txt + - kind: USER + value: openvino + +# commands: +# - "@nuclio.postCopy" +# - curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip +# - unzip dextr_model_v1.zip +# - pip3 install -r requirements.txt + + eventTimeout: 30s triggers: myHttpTrigger: maxWorkers: 2 diff --git a/components/dextr/nuclio/function/inference_engine.py b/components/dextr/nuclio/inference_engine.py similarity index 100% rename from components/dextr/nuclio/function/inference_engine.py rename to components/dextr/nuclio/inference_engine.py diff --git a/components/dextr/nuclio/function/main.py b/components/dextr/nuclio/main.py similarity index 100% rename from components/dextr/nuclio/function/main.py rename to components/dextr/nuclio/main.py diff --git a/components/dextr/nuclio/pip b/components/dextr/nuclio/pip new file mode 100755 index 000000000000..bceaba4c962f --- /dev/null +++ b/components/dextr/nuclio/pip @@ -0,0 +1,4 @@ +#!/bin/bash + +pip3 $@ + diff --git a/components/dextr/nuclio/function/python3 b/components/dextr/nuclio/python3 similarity index 100% rename from components/dextr/nuclio/function/python3 rename to components/dextr/nuclio/python3 diff --git a/components/dextr/nuclio/function/requirements.txt b/components/dextr/nuclio/requirements.txt similarity index 100% rename from components/dextr/nuclio/function/requirements.txt rename to components/dextr/nuclio/requirements.txt diff --git a/components/dextr/nuclio/run.sh b/components/dextr/nuclio/run.sh index c9e40575275b..841ff037573a 100755 --- a/components/dextr/nuclio/run.sh +++ b/components/dextr/nuclio/run.sh @@ -1,3 +1,3 @@ #!/bin/bash -http POST http://localhost:50953 < ./input.json +http POST http://localhost:53462 < ./input.json From 2920c5b0f7d93bb9fa89c670c29ff1aac41e93fb Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Thu, 23 Apr 2020 16:00:37 +0300 Subject: [PATCH 03/98] Improve nuclio prototype for dextr. --- components/dextr/nuclio/Dockerfile | 41 --------------------------- components/dextr/nuclio/deploy.sh | 3 -- components/dextr/nuclio/function.yaml | 20 +++++-------- components/dextr/nuclio/pip | 4 --- 4 files changed, 7 insertions(+), 61 deletions(-) delete mode 100644 components/dextr/nuclio/Dockerfile delete mode 100755 components/dextr/nuclio/deploy.sh delete mode 100755 components/dextr/nuclio/pip diff --git a/components/dextr/nuclio/Dockerfile b/components/dextr/nuclio/Dockerfile deleted file mode 100644 index bf9d52a1c875..000000000000 --- a/components/dextr/nuclio/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -# https://github.com/nuclio/nuclio/blob/master/docs/reference/runtimes/python/python-reference.md#python-reference -ARG NUCLIO_LABEL=0.7.1 -ARG NUCLIO_ARCH=amd64 -ARG NUCLIO_BASE_IMAGE=openvino/ubuntu18_runtime:2020.2 -ARG NUCLIO_ONBUILD_IMAGE=nuclio/handler-builder-python-onbuild:${NUCLIO_LABEL}-${NUCLIO_ARCH} - -# Supplies processor uhttpc, used for healthcheck -FROM nuclio/uhttpc:latest-amd64 as uhttpc - -# Supplies processor binary, wrapper -FROM ${NUCLIO_ONBUILD_IMAGE} as processor - -# From the base image -FROM ${NUCLIO_BASE_IMAGE} - -# Copy required objects from the suppliers -COPY --from=processor /home/nuclio/bin/processor /usr/local/bin/processor -COPY --from=processor /home/nuclio/bin/py /opt/nuclio/ -COPY --from=uhttpc /home/nuclio/bin/uhttpc /usr/local/bin/uhttpc - -RUN pip3 install nuclio-sdk msgpack - -# Readiness probe -HEALTHCHECK --interval=1s --timeout=3s CMD /usr/local/bin/uhttpc --url http://127.0.0.1:8082/ready || exit 1 - -# USER CONTENT -USER root -WORKDIR /opt/nuclio -ENV PATH=/opt/nuclio:${PATH} -RUN curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -RUN unzip dextr_model_v1.zip -COPY ./function/requirements.txt ./ -RUN pip3 install -r requirements.txt -COPY ./function ./ -COPY ./function.yaml /etc/nuclio/config/processor/processor.yaml - -USER openvino -# END OF USER CONTENT - -# Run processor with configuration and platform configuration -CMD [ "processor" ] diff --git a/components/dextr/nuclio/deploy.sh b/components/dextr/nuclio/deploy.sh deleted file mode 100755 index aaf34d78aeaa..000000000000 --- a/components/dextr/nuclio/deploy.sh +++ /dev/null @@ -1,3 +0,0 @@ -docker build -t cvat/dextr:latest . -nuctl deploy -f ./function.yaml -#nuctl deploy dextr --run-image cvat/dextr:latest --runtime "python:3.6" --handler "main:handler" --platform local diff --git a/components/dextr/nuclio/function.yaml b/components/dextr/nuclio/function.yaml index 20418676562d..257d8617ef31 100644 --- a/components/dextr/nuclio/function.yaml +++ b/components/dextr/nuclio/function.yaml @@ -1,5 +1,3 @@ -apiVersion: "nuclio.io/v1" -kind: "NuclioFunction" metadata: name: dextr namespace: cvat @@ -8,6 +6,11 @@ spec: description: Deep Extreme Cut runtime: "python:3.6" handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/python3 + build: image: cvat/dextr baseImage: openvino/ubuntu18_runtime:2020.2 @@ -16,12 +19,10 @@ spec: preCopy: - kind: USER value: root - - kind: WORKDIR value: /opt/nuclio - - - kind: ENV - value: PATH=/opt/nuclio:${PATH} + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip postCopy: - kind: RUN @@ -33,13 +34,6 @@ spec: - kind: USER value: openvino -# commands: -# - "@nuclio.postCopy" -# - curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -# - unzip dextr_model_v1.zip -# - pip3 install -r requirements.txt - - eventTimeout: 30s triggers: myHttpTrigger: maxWorkers: 2 diff --git a/components/dextr/nuclio/pip b/components/dextr/nuclio/pip deleted file mode 100755 index bceaba4c962f..000000000000 --- a/components/dextr/nuclio/pip +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -pip3 $@ - From 2a76d71b71b427bb1d6aced4733701eb1dcd860f Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 25 Apr 2020 11:50:26 +0300 Subject: [PATCH 04/98] Dummy lambda manager --- .../auto_segmentation/nuclio/function.yaml | 41 +++ components/tf_annotation/nuclio/function.yaml | 36 ++ components/tf_annotation/nuclio/main.py | 0 .../tf_annotation/nuclio/tf_inference.py | 324 ++++++++++++++++++ cvat/apps/lambda_manager/__init__.py | 0 cvat/apps/lambda_manager/admin.py | 3 + cvat/apps/lambda_manager/apps.py | 5 + .../lambda_manager/migrations/__init__.py | 0 cvat/apps/lambda_manager/models.py | 3 + cvat/apps/lambda_manager/tests.py | 3 + cvat/apps/lambda_manager/urls.py | 23 ++ cvat/apps/lambda_manager/views.py | 7 + docker-compose.yml | 11 + 13 files changed, 456 insertions(+) create mode 100644 components/auto_segmentation/nuclio/function.yaml create mode 100644 components/tf_annotation/nuclio/function.yaml create mode 100644 components/tf_annotation/nuclio/main.py create mode 100644 components/tf_annotation/nuclio/tf_inference.py create mode 100644 cvat/apps/lambda_manager/__init__.py create mode 100644 cvat/apps/lambda_manager/admin.py create mode 100644 cvat/apps/lambda_manager/apps.py create mode 100644 cvat/apps/lambda_manager/migrations/__init__.py create mode 100644 cvat/apps/lambda_manager/models.py create mode 100644 cvat/apps/lambda_manager/tests.py create mode 100644 cvat/apps/lambda_manager/urls.py create mode 100644 cvat/apps/lambda_manager/views.py diff --git a/components/auto_segmentation/nuclio/function.yaml b/components/auto_segmentation/nuclio/function.yaml new file mode 100644 index 000000000000..257d8617ef31 --- /dev/null +++ b/components/auto_segmentation/nuclio/function.yaml @@ -0,0 +1,41 @@ +metadata: + name: dextr + namespace: cvat + +spec: + description: Deep Extreme Cut + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/python3 + + build: + image: cvat/dextr + baseImage: openvino/ubuntu18_runtime:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + + postCopy: + - kind: RUN + value: curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip + - kind: RUN + value: unzip dextr_model_v1.zip + - kind: RUN + value: pip3 install -r requirements.txt + - kind: USER + value: openvino + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 diff --git a/components/tf_annotation/nuclio/function.yaml b/components/tf_annotation/nuclio/function.yaml new file mode 100644 index 000000000000..a661d7dc6952 --- /dev/null +++ b/components/tf_annotation/nuclio/function.yaml @@ -0,0 +1,36 @@ +metadata: + name: tf_faster_rcnn_inception_resnet_v2_atrous_coco + namespace: cvat + +spec: + description: TF Object Detection API (faster RCNN) + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + + build: + image: cvat/tf_faster_rcnn_inception_resnet_v2_atrous_coco + baseImage: tensorflow/tensorflow:2.0.1-py3 + + directives: + preCopy: + - kind: RUN + value: apt install curl + - kind: WORKDIR + value: /opt/nuclio + + postCopy: + - kind: RUN + value: curl http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz -o model.tar.gz + - kind: RUN + value: tar -xzf model.tar.gz && rm model.tar.gz + - kind: RUN + value: ln -s faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28 rcnn + - kind: RUN + value: ln -s rcnn/frozen_inference_graph.pb rcnn/inference_graph.pb + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 diff --git a/components/tf_annotation/nuclio/main.py b/components/tf_annotation/nuclio/main.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/components/tf_annotation/nuclio/tf_inference.py b/components/tf_annotation/nuclio/tf_inference.py new file mode 100644 index 000000000000..9ca957e2af9e --- /dev/null +++ b/components/tf_annotation/nuclio/tf_inference.py @@ -0,0 +1,324 @@ + +# Copyright (C) 2018-2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest +from rest_framework.decorators import api_view +from rules.contrib.views import permission_required, objectgetter +from cvat.apps.authentication.decorators import login_required +from cvat.apps.engine.models import Task as TaskModel +from cvat.apps.engine.serializers import LabeledDataSerializer +from cvat.apps.engine.annotation import put_task_data +from cvat.apps.engine.frame_provider import FrameProvider + +import os + +import tensorflow as tf +import numpy as np + +from PIL import Image +from cvat.apps.engine.log import slogger + + +def load_image_into_numpy(image): + (im_width, im_height) = image.size + return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8) + + +def run_inference_engine_annotation(image_list, labels_mapping, treshold): + from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network + + def _normalize_box(box, w, h, dw, dh): + xmin = min(int(box[0] * dw * w), w) + ymin = min(int(box[1] * dh * h), h) + xmax = min(int(box[2] * dw * w), w) + ymax = min(int(box[3] * dh * h), h) + return xmin, ymin, xmax, ymax + + result = {} + MODEL_PATH = os.environ.get('TF_ANNOTATION_MODEL_PATH') + if MODEL_PATH is None: + raise OSError('Model path env not found in the system.') + + core_or_plugin = make_plugin_or_core() + network = make_network('{}.xml'.format(MODEL_PATH), '{}.bin'.format(MODEL_PATH)) + input_blob_name = next(iter(network.inputs)) + output_blob_name = next(iter(network.outputs)) + if getattr(core_or_plugin, 'load_network', False): + executable_network = core_or_plugin.load_network(network, 'CPU') + else: + executable_network = core_or_plugin.load(network=network) + job = rq.get_current_job() + + del network + + try: + for image_num, im_name in enumerate(image_list): + + job.refresh() + if 'cancel' in job.meta: + del job.meta['cancel'] + job.save() + return None + job.meta['progress'] = image_num * 100 / len(image_list) + job.save_meta() + + image = Image.open(im_name) + width, height = image.size + image.thumbnail((600, 600), Image.ANTIALIAS) + dwidth, dheight = 600 / image.size[0], 600 / image.size[1] + image = image.crop((0, 0, 600, 600)) + image_np = load_image_into_numpy(image) + image_np = np.transpose(image_np, (2, 0, 1)) + prediction = executable_network.infer(inputs={input_blob_name: image_np[np.newaxis, ...]})[output_blob_name][0][0] + for obj in prediction: + obj_class = int(obj[1]) + obj_value = obj[2] + if obj_class and obj_class in labels_mapping and obj_value >= treshold: + label = labels_mapping[obj_class] + if label not in result: + result[label] = [] + xmin, ymin, xmax, ymax = _normalize_box(obj[3:7], width, height, dwidth, dheight) + result[label].append([image_num, xmin, ymin, xmax, ymax]) + finally: + del executable_network + del plugin + + return result + + +def run_tensorflow_annotation(frame_provider, labels_mapping, treshold): + def _normalize_box(box, w, h): + xmin = int(box[1] * w) + ymin = int(box[0] * h) + xmax = int(box[3] * w) + ymax = int(box[2] * h) + return xmin, ymin, xmax, ymax + + result = {} + model_path = os.environ.get('TF_ANNOTATION_MODEL_PATH') + if model_path is None: + raise OSError('Model path env not found in the system.') + job = rq.get_current_job() + + detection_graph = tf.Graph() + with detection_graph.as_default(): + od_graph_def = tf.GraphDef() + with tf.gfile.GFile(model_path + '.pb', 'rb') as fid: + serialized_graph = fid.read() + od_graph_def.ParseFromString(serialized_graph) + tf.import_graph_def(od_graph_def, name='') + + try: + config = tf.ConfigProto() + config.gpu_options.allow_growth=True + sess = tf.Session(graph=detection_graph, config=config) + frames = frame_provider.get_frames(frame_provider.Quality.ORIGINAL) + for image_num, (image, _) in enumerate(frames): + + job.refresh() + if 'cancel' in job.meta: + del job.meta['cancel'] + job.save() + return None + job.meta['progress'] = image_num * 100 / len(frame_provider) + job.save_meta() + + image = Image.open(image) + width, height = image.size + if width > 1920 or height > 1080: + image = image.resize((width // 2, height // 2), Image.ANTIALIAS) + image_np = load_image_into_numpy(image) + image_np_expanded = np.expand_dims(image_np, axis=0) + + image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') + boxes = detection_graph.get_tensor_by_name('detection_boxes:0') + scores = detection_graph.get_tensor_by_name('detection_scores:0') + classes = detection_graph.get_tensor_by_name('detection_classes:0') + num_detections = detection_graph.get_tensor_by_name('num_detections:0') + (boxes, scores, classes, num_detections) = sess.run([boxes, scores, classes, num_detections], feed_dict={image_tensor: image_np_expanded}) + + for i in range(len(classes[0])): + if classes[0][i] in labels_mapping.keys(): + if scores[0][i] >= treshold: + xmin, ymin, xmax, ymax = _normalize_box(boxes[0][i], width, height) + label = labels_mapping[classes[0][i]] + if label not in result: + result[label] = [] + result[label].append([image_num, xmin, ymin, xmax, ymax]) + finally: + sess.close() + del sess + return result + +def convert_to_cvat_format(data): + result = { + "tracks": [], + "shapes": [], + "tags": [], + "version": 0, + } + + for label in data: + boxes = data[label] + for box in boxes: + result['shapes'].append({ + "type": "rectangle", + "label_id": label, + "frame": box[0], + "points": [box[1], box[2], box[3], box[4]], + "z_order": 0, + "group": None, + "occluded": False, + "attributes": [], + }) + + return result + +def create_thread(tid, labels_mapping, user): + try: + TRESHOLD = 0.5 + # Init rq job + job = rq.get_current_job() + job.meta['progress'] = 0 + job.save_meta() + # Get job indexes and segment length + db_task = TaskModel.objects.get(pk=tid) + # Get image list + image_list = FrameProvider(db_task.data) + + # Run auto annotation by tf + result = None + slogger.glob.info("tf annotation with tensorflow framework for task {}".format(tid)) + result = run_tensorflow_annotation(image_list, labels_mapping, TRESHOLD) + + if result is None: + slogger.glob.info('tf annotation for task {} canceled by user'.format(tid)) + return + + # Modify data format and save + result = convert_to_cvat_format(result) + serializer = LabeledDataSerializer(data = result) + if serializer.is_valid(raise_exception=True): + put_task_data(tid, user, result) + slogger.glob.info('tf annotation for task {} done'.format(tid)) + except Exception as ex: + try: + slogger.task[tid].exception('exception was occured during tf annotation of the task', exc_info=True) + except: + slogger.glob.exception('exception was occured during tf annotation of the task {}'.format(tid), exc_info=True) + raise ex + + +@login_required +@permission_required(perm=['engine.task.change'], + fn=objectgetter(TaskModel, 'tid'), raise_exception=True) +def create(request, tid): + slogger.glob.info('tf annotation create request for task {}'.format(tid)) + try: + db_task = TaskModel.objects.get(pk=tid) + queue = django_rq.get_queue('low') + job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) + if job is not None and (job.is_started or job.is_queued): + raise Exception("The process is already running") + + db_labels = db_task.label_set.prefetch_related('attributespec_set').all() + db_labels = {db_label.id:db_label.name for db_label in db_labels} + + tf_annotation_labels = { + "person": 1, "bicycle": 2, "car": 3, "motorcycle": 4, "airplane": 5, + "bus": 6, "train": 7, "truck": 8, "boat": 9, "traffic_light": 10, + "fire_hydrant": 11, "stop_sign": 13, "parking_meter": 14, "bench": 15, + "bird": 16, "cat": 17, "dog": 18, "horse": 19, "sheep": 20, "cow": 21, + "elephant": 22, "bear": 23, "zebra": 24, "giraffe": 25, "backpack": 27, + "umbrella": 28, "handbag": 31, "tie": 32, "suitcase": 33, "frisbee": 34, + "skis": 35, "snowboard": 36, "sports_ball": 37, "kite": 38, "baseball_bat": 39, + "baseball_glove": 40, "skateboard": 41, "surfboard": 42, "tennis_racket": 43, + "bottle": 44, "wine_glass": 46, "cup": 47, "fork": 48, "knife": 49, "spoon": 50, + "bowl": 51, "banana": 52, "apple": 53, "sandwich": 54, "orange": 55, "broccoli": 56, + "carrot": 57, "hot_dog": 58, "pizza": 59, "donut": 60, "cake": 61, "chair": 62, + "couch": 63, "potted_plant": 64, "bed": 65, "dining_table": 67, "toilet": 70, + "tv": 72, "laptop": 73, "mouse": 74, "remote": 75, "keyboard": 76, "cell_phone": 77, + "microwave": 78, "oven": 79, "toaster": 80, "sink": 81, "refrigerator": 83, + "book": 84, "clock": 85, "vase": 86, "scissors": 87, "teddy_bear": 88, "hair_drier": 89, + "toothbrush": 90 + } + + labels_mapping = {} + for key, labels in db_labels.items(): + if labels in tf_annotation_labels.keys(): + labels_mapping[tf_annotation_labels[labels]] = key + + if not len(labels_mapping.values()): + raise Exception('No labels found for tf annotation') + + # Run tf annotation job + queue.enqueue_call(func=create_thread, + args=(tid, labels_mapping, request.user), + job_id='tf_annotation.create/{}'.format(tid), + timeout=604800) # 7 days + + slogger.task[tid].info('tensorflow annotation job enqueued with labels {}'.format(labels_mapping)) + + except Exception as ex: + try: + slogger.task[tid].exception("exception was occured during tensorflow annotation request", exc_info=True) + except: + pass + return HttpResponseBadRequest(str(ex)) + + return HttpResponse() + +@login_required +@permission_required(perm=['engine.task.access'], + fn=objectgetter(TaskModel, 'tid'), raise_exception=True) +def check(request, tid): + try: + queue = django_rq.get_queue('low') + job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) + if job is not None and 'cancel' in job.meta: + return JsonResponse({'status': 'finished'}) + data = {} + if job is None: + data['status'] = 'unknown' + elif job.is_queued: + data['status'] = 'queued' + elif job.is_started: + data['status'] = 'started' + data['progress'] = job.meta['progress'] + elif job.is_finished: + data['status'] = 'finished' + job.delete() + else: + data['status'] = 'failed' + data['stderr'] = job.exc_info + job.delete() + + except Exception: + data['status'] = 'unknown' + + return JsonResponse(data) + + +@login_required +@permission_required(perm=['engine.task.change'], + fn=objectgetter(TaskModel, 'tid'), raise_exception=True) +def cancel(request, tid): + try: + queue = django_rq.get_queue('low') + job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) + if job is None or job.is_finished or job.is_failed: + raise Exception('Task is not being annotated currently') + elif 'cancel' not in job.meta: + job.meta['cancel'] = True + job.save() + + except Exception as ex: + try: + slogger.task[tid].exception("cannot cancel tensorflow annotation for task #{}".format(tid), exc_info=True) + except: + pass + return HttpResponseBadRequest(str(ex)) + + return HttpResponse() diff --git a/cvat/apps/lambda_manager/__init__.py b/cvat/apps/lambda_manager/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cvat/apps/lambda_manager/admin.py b/cvat/apps/lambda_manager/admin.py new file mode 100644 index 000000000000..8c38f3f3dad5 --- /dev/null +++ b/cvat/apps/lambda_manager/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/cvat/apps/lambda_manager/apps.py b/cvat/apps/lambda_manager/apps.py new file mode 100644 index 000000000000..eda3a97180c6 --- /dev/null +++ b/cvat/apps/lambda_manager/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class LambdaManagerConfig(AppConfig): + name = 'lambda_manager' diff --git a/cvat/apps/lambda_manager/migrations/__init__.py b/cvat/apps/lambda_manager/migrations/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cvat/apps/lambda_manager/models.py b/cvat/apps/lambda_manager/models.py new file mode 100644 index 000000000000..71a836239075 --- /dev/null +++ b/cvat/apps/lambda_manager/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/cvat/apps/lambda_manager/tests.py b/cvat/apps/lambda_manager/tests.py new file mode 100644 index 000000000000..7ce503c2dd97 --- /dev/null +++ b/cvat/apps/lambda_manager/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/cvat/apps/lambda_manager/urls.py b/cvat/apps/lambda_manager/urls.py new file mode 100644 index 000000000000..c26c08842ec1 --- /dev/null +++ b/cvat/apps/lambda_manager/urls.py @@ -0,0 +1,23 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from django.urls import path +from rest_framework import routers +from django.urls import include +from . import views + +router = routers.DefaultRouter(trailing_slash=False) +router.register('functions', views.FunctionViewSet) + +# GET /api/v1/lambda/functions - get list of functions +# POST /api/v1/lambda/functions - add one more function +# GET /api/v1/lambda/functions/ - get information about the function +# DEL /api/v1/lambda/functions/ - delete a function +# POST /api/v1/labmda/functions//requests - call a function +# GET /api/v1/lambda/functions//requests - get list of requests +# GET /api/v1/lambda/functions//requests/ - get information about the call +# DEL /api/v1/lambda/functions//requests/ - cancel a request (don't delete) +urlpatterns = [ + path('api/v1/lambda', include((router.urls, 'cvat'), namespace='v1')) +] \ No newline at end of file diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py new file mode 100644 index 000000000000..ce9477584b49 --- /dev/null +++ b/cvat/apps/lambda_manager/views.py @@ -0,0 +1,7 @@ +from django.shortcuts import render +from rest_framework import viewsets + +# Create your views here. + +class FunctionViewSet(viewsets.ViewSet): + pass \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3bb1eb701515..4b32341bc54e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -94,6 +94,17 @@ services: - ./cvat_proxy/conf.d/cvat.conf.template:/etc/nginx/conf.d/cvat.conf.template:ro command: /bin/sh -c "envsubst '$$CVAT_HOST' < /etc/nginx/conf.d/cvat.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" + serverless: + container_name: nuclio-dashboard + image: quay.io/nuclio/dashboard:stable-amd64 + networks: + default: + aliases: + - nuclio + volumes: + - /tmp:/tmp + - /var/run/docker.sock:/var/run/docker.sock + volumes: cvat_db: cvat_data: From 13978eca752c42fcc93e5cd5b6c2a88cdb0c1259 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 6 Apr 2020 10:41:08 +0300 Subject: [PATCH 05/98] OpenFaaS prototype (dextr.bin and dextr.xml are empty). --- serverless/dextr.yml | 12 +++ serverless/dextr/Dockerfile | 25 +++++++ serverless/dextr/dextr.bin | 1 + serverless/dextr/dextr.py | 106 +++++++++++++++++++++++++++ serverless/dextr/dextr.xml | 1 + serverless/dextr/index.py | 20 +++++ serverless/dextr/index.sh | 2 + serverless/dextr/inference_engine.py | 53 ++++++++++++++ serverless/dextr/requirements.txt | 1 + 9 files changed, 221 insertions(+) create mode 100644 serverless/dextr.yml create mode 100644 serverless/dextr/Dockerfile create mode 100644 serverless/dextr/dextr.bin create mode 100644 serverless/dextr/dextr.py create mode 100644 serverless/dextr/dextr.xml create mode 100644 serverless/dextr/index.py create mode 100644 serverless/dextr/index.sh create mode 100644 serverless/dextr/inference_engine.py create mode 100644 serverless/dextr/requirements.txt diff --git a/serverless/dextr.yml b/serverless/dextr.yml new file mode 100644 index 000000000000..d7d748e08a15 --- /dev/null +++ b/serverless/dextr.yml @@ -0,0 +1,12 @@ +version: 1.0 +provider: + name: openfaas + gateway: http://127.0.0.1:8080 +functions: + dextr: + lang: dockerfile + handler: ./dextr + image: dextr:latest + environment: + read_timeout: 20s + write_timeout: 20s diff --git a/serverless/dextr/Dockerfile b/serverless/dextr/Dockerfile new file mode 100644 index 000000000000..f57d43373faf --- /dev/null +++ b/serverless/dextr/Dockerfile @@ -0,0 +1,25 @@ +FROM openfaas/classic-watchdog:0.18.1 as watchdog + +FROM openvino/ubuntu18_runtime:2020.1 + +USER root + +COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog +RUN chmod +x /usr/bin/fwatchdog +WORKDIR /home/openvino +RUN apt update && apt install -y python3-dev libpython3.6 + +USER openvino +COPY dextr.* *.py *.sh requirements.txt /home/openvino/ +RUN pip3 install --user -r requirements.txt + +# Populate example here - i.e. "cat", "sha512sum" or "node index.js" +ENV fprocess="bash index.sh" +# Set to true to see request in function logs +ENV write_debug="true" + +EXPOSE 8080 + +HEALTHCHECK --interval=3s CMD [ -e /tmp/.lock ] || exit 1 + +CMD ["fwatchdog"] diff --git a/serverless/dextr/dextr.bin b/serverless/dextr/dextr.bin new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/serverless/dextr/dextr.bin @@ -0,0 +1 @@ + diff --git a/serverless/dextr/dextr.py b/serverless/dextr/dextr.py new file mode 100644 index 000000000000..57ce81f52ec4 --- /dev/null +++ b/serverless/dextr/dextr.py @@ -0,0 +1,106 @@ +# Copyright (C) 2018-2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from inference_engine import make_plugin_or_core, make_network + +import os +import cv2 +import numpy as np + +_IE_CPU_EXTENSION = os.getenv("IE_CPU_EXTENSION", "libcpu_extension_avx2.so") +_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) + +_DEXTR_PADDING = 50 +_DEXTR_TRESHOLD = 0.9 +_DEXTR_SIZE = 512 + +class DEXTR_HANDLER: + def __init__(self): + self._plugin = None + self._network = None + self._exec_network = None + self._input_blob = None + self._output_blob = None + + # Input: + # image: PIL image + # points: [[x1,y1], [x2,y2], [x3,y3], [x4,y4], ...] + # Output: + # polygon: [[x1,y1], [x2,y2], [x3,y3], [x4,y4], ...] + def handle(self, image, points): + # Lazy initialization + if not self._plugin: + print("Initialization... 0%") + self._plugin = make_plugin_or_core() + self._network = make_network('dextr.xml', 'dextr.bin') + self._input_blob = next(iter(self._network.inputs)) + self._output_blob = next(iter(self._network.outputs)) + if getattr(self._plugin, 'load_network', False): + self._exec_network = self._plugin.load_network(self._network, 'CPU') + else: + self._exec_network = self._plugin.load(network=self._network) + print("Initialization... 100%") + + numpy_image = np.array(image) + points = np.asarray(points, dtype=int) + bounding_box = ( + max(min(points[:, 0]) - _DEXTR_PADDING, 0), + max(min(points[:, 1]) - _DEXTR_PADDING, 0), + min(max(points[:, 0]) + _DEXTR_PADDING, numpy_image.shape[1] - 1), + min(max(points[:, 1]) + _DEXTR_PADDING, numpy_image.shape[0] - 1) + ) + + # Prepare an image + numpy_cropped = np.array(image.crop(bounding_box)) + resized = cv2.resize(numpy_cropped, (_DEXTR_SIZE, _DEXTR_SIZE), + interpolation = cv2.INTER_CUBIC).astype(np.float32) + + # Make a heatmap + points = points - [min(points[:, 0]), min(points[:, 1])] + [_DEXTR_PADDING, _DEXTR_PADDING] + points = (points * [_DEXTR_SIZE / numpy_cropped.shape[1], _DEXTR_SIZE / numpy_cropped.shape[0]]).astype(int) + heatmap = np.zeros(shape=resized.shape[:2], dtype=np.float64) + for point in points: + gaussian_x_axis = np.arange(0, _DEXTR_SIZE, 1, float) - point[0] + gaussian_y_axis = np.arange(0, _DEXTR_SIZE, 1, float)[:, np.newaxis] - point[1] + gaussian = np.exp(-4 * np.log(2) * ((gaussian_x_axis ** 2 + gaussian_y_axis ** 2) / 100)).astype(np.float64) + heatmap = np.maximum(heatmap, gaussian) + cv2.normalize(heatmap, heatmap, 0, 255, cv2.NORM_MINMAX) + + # Concat an image and a heatmap + input_dextr = np.concatenate((resized, heatmap[:, :, np.newaxis].astype(resized.dtype)), axis=2) + input_dextr = input_dextr.transpose((2,0,1)) + + pred = self._exec_network.infer(inputs={self._input_blob: input_dextr[np.newaxis, ...]})[self._output_blob][0, 0, :, :] + pred = cv2.resize(pred, tuple(reversed(numpy_cropped.shape[:2])), interpolation = cv2.INTER_CUBIC) + result = np.zeros(numpy_image.shape[:2]) + result[bounding_box[1]:bounding_box[1] + pred.shape[0], bounding_box[0]:bounding_box[0] + pred.shape[1]] = pred > _DEXTR_TRESHOLD + + # Convert a mask to a polygon + result = np.array(result, dtype=np.uint8) + cv2.normalize(result,result,0,255,cv2.NORM_MINMAX) + contours = None + if int(cv2.__version__.split('.')[0]) > 3: + contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[0] + else: + contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[1] + + contours = max(contours, key=lambda arr: arr.size) + if contours.shape.count(1): + contours = np.squeeze(contours) + if contours.size < 3 * 2: + raise Exception('Less then three point have been detected. Can not build a polygon.') + + result = [] + for point in contours: + result.append([int(point[0]), int(point[1])]) + + return result + + def __del__(self): + if self._exec_network: + del self._exec_network + if self._network: + del self._network + if self._plugin: + del self._plugin diff --git a/serverless/dextr/dextr.xml b/serverless/dextr/dextr.xml new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/serverless/dextr/dextr.xml @@ -0,0 +1 @@ + diff --git a/serverless/dextr/index.py b/serverless/dextr/index.py new file mode 100644 index 000000000000..7acce649848e --- /dev/null +++ b/serverless/dextr/index.py @@ -0,0 +1,20 @@ +import json +import base64 +from PIL import Image +import io +import dextr +import sys + +dextr_handler = dextr.DEXTR_HANDLER() + +def handle(req): + data = json.loads(req) + points = data["points"] + buf = io.BytesIO(base64.b64decode(data["image"])) + image = Image.open(buf) + + polygon = dextr_handler.handle(image, points) + return json.dumps(polygon) + +output = handle(sys.stdin.read()) +print(output) \ No newline at end of file diff --git a/serverless/dextr/index.sh b/serverless/dextr/index.sh new file mode 100644 index 000000000000..0e36134c7dda --- /dev/null +++ b/serverless/dextr/index.sh @@ -0,0 +1,2 @@ +. /opt/intel/openvino/bin/setupvars.sh > /dev/null +python3 index.py <&0 diff --git a/serverless/dextr/inference_engine.py b/serverless/dextr/inference_engine.py new file mode 100644 index 000000000000..fb6b543d34e2 --- /dev/null +++ b/serverless/dextr/inference_engine.py @@ -0,0 +1,53 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version + +import subprocess +import os +import platform + +_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) + + +def _check_instruction(instruction): + return instruction == str.strip( + subprocess.check_output( + 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True + ).decode('utf-8') + ) + + +def make_plugin_or_core(): + version = get_version() + use_core_openvino = False + try: + major, minor, reference = [int(x) for x in version.split('.')] + if major >= 2 and minor >= 1 and reference >= 37988: + use_core_openvino = True + except Exception: + pass + + if use_core_openvino: + ie = IECore() + return ie + + if _IE_PLUGINS_PATH is None: + raise OSError('Inference engine plugin path env not found in the system.') + + plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH]) + if (_check_instruction('avx2')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so')) + elif (_check_instruction('sse4')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so')) + elif platform.system() == 'Darwin': + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension.dylib')) + else: + raise Exception('Inference engine requires a support of avx2 or sse4.') + + return plugin + + +def make_network(model, weights): + return IENetwork(model = model, weights = weights) diff --git a/serverless/dextr/requirements.txt b/serverless/dextr/requirements.txt new file mode 100644 index 000000000000..5873a2224bf9 --- /dev/null +++ b/serverless/dextr/requirements.txt @@ -0,0 +1 @@ +Pillow \ No newline at end of file From 0cd4127b58617d4efcd20ef476c46ab01a178967 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 25 Apr 2020 11:57:11 +0300 Subject: [PATCH 06/98] Moved openfaas prototype. --- {serverless/dextr => components/dextr/openfaas}/Dockerfile | 0 {serverless/dextr => components/dextr/openfaas}/dextr.py | 0 {serverless => components/dextr/openfaas}/dextr.yml | 2 +- {serverless/dextr => components/dextr/openfaas}/index.py | 0 {serverless/dextr => components/dextr/openfaas}/index.sh | 0 .../dextr => components/dextr/openfaas}/inference_engine.py | 0 .../dextr => components/dextr/openfaas}/requirements.txt | 0 serverless/dextr/dextr.bin | 1 - serverless/dextr/dextr.xml | 1 - 9 files changed, 1 insertion(+), 3 deletions(-) rename {serverless/dextr => components/dextr/openfaas}/Dockerfile (100%) rename {serverless/dextr => components/dextr/openfaas}/dextr.py (100%) rename {serverless => components/dextr/openfaas}/dextr.yml (90%) rename {serverless/dextr => components/dextr/openfaas}/index.py (100%) rename {serverless/dextr => components/dextr/openfaas}/index.sh (100%) rename {serverless/dextr => components/dextr/openfaas}/inference_engine.py (100%) rename {serverless/dextr => components/dextr/openfaas}/requirements.txt (100%) delete mode 100644 serverless/dextr/dextr.bin delete mode 100644 serverless/dextr/dextr.xml diff --git a/serverless/dextr/Dockerfile b/components/dextr/openfaas/Dockerfile similarity index 100% rename from serverless/dextr/Dockerfile rename to components/dextr/openfaas/Dockerfile diff --git a/serverless/dextr/dextr.py b/components/dextr/openfaas/dextr.py similarity index 100% rename from serverless/dextr/dextr.py rename to components/dextr/openfaas/dextr.py diff --git a/serverless/dextr.yml b/components/dextr/openfaas/dextr.yml similarity index 90% rename from serverless/dextr.yml rename to components/dextr/openfaas/dextr.yml index d7d748e08a15..e2917079f32c 100644 --- a/serverless/dextr.yml +++ b/components/dextr/openfaas/dextr.yml @@ -5,7 +5,7 @@ provider: functions: dextr: lang: dockerfile - handler: ./dextr + handler: . image: dextr:latest environment: read_timeout: 20s diff --git a/serverless/dextr/index.py b/components/dextr/openfaas/index.py similarity index 100% rename from serverless/dextr/index.py rename to components/dextr/openfaas/index.py diff --git a/serverless/dextr/index.sh b/components/dextr/openfaas/index.sh similarity index 100% rename from serverless/dextr/index.sh rename to components/dextr/openfaas/index.sh diff --git a/serverless/dextr/inference_engine.py b/components/dextr/openfaas/inference_engine.py similarity index 100% rename from serverless/dextr/inference_engine.py rename to components/dextr/openfaas/inference_engine.py diff --git a/serverless/dextr/requirements.txt b/components/dextr/openfaas/requirements.txt similarity index 100% rename from serverless/dextr/requirements.txt rename to components/dextr/openfaas/requirements.txt diff --git a/serverless/dextr/dextr.bin b/serverless/dextr/dextr.bin deleted file mode 100644 index 8b137891791f..000000000000 --- a/serverless/dextr/dextr.bin +++ /dev/null @@ -1 +0,0 @@ - diff --git a/serverless/dextr/dextr.xml b/serverless/dextr/dextr.xml deleted file mode 100644 index 8b137891791f..000000000000 --- a/serverless/dextr/dextr.xml +++ /dev/null @@ -1 +0,0 @@ - From d089f4962d1b88f533739d2bfe99053fad938045 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 25 Apr 2020 16:46:52 +0300 Subject: [PATCH 07/98] Add comments --- cvat/apps/lambda_manager/urls.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cvat/apps/lambda_manager/urls.py b/cvat/apps/lambda_manager/urls.py index c26c08842ec1..603a5aec44de 100644 --- a/cvat/apps/lambda_manager/urls.py +++ b/cvat/apps/lambda_manager/urls.py @@ -9,15 +9,18 @@ router = routers.DefaultRouter(trailing_slash=False) router.register('functions', views.FunctionViewSet) +router.register('requests', views.RequestViewSet) # GET /api/v1/lambda/functions - get list of functions # POST /api/v1/lambda/functions - add one more function # GET /api/v1/lambda/functions/ - get information about the function # DEL /api/v1/lambda/functions/ - delete a function -# POST /api/v1/labmda/functions//requests - call a function -# GET /api/v1/lambda/functions//requests - get list of requests -# GET /api/v1/lambda/functions//requests/ - get information about the call -# DEL /api/v1/lambda/functions//requests/ - cancel a request (don't delete) +# POST /api/v1/labmda/online-requests - call a function +# { "function": "", "mode": "online|offline", "job": "", "frame": "", +# "points": [...], } +# GET /api/v1/lambda/requests - get list of requests +# GET /api/v1/lambda/requests/ - get status of the request +# DEL /api/v1/lambda/requests/ - cancel a request (don't delete) urlpatterns = [ path('api/v1/lambda', include((router.urls, 'cvat'), namespace='v1')) ] \ No newline at end of file From 34817213538b5236cafdbd9d0318c58b575e974c Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sun, 26 Apr 2020 17:15:20 +0300 Subject: [PATCH 08/98] Add serializers and HLD for lambda_manager --- cvat/apps/lambda_manager/docs/hld.md | 23 ++++++++++++ cvat/apps/lambda_manager/serializers.py | 49 +++++++++++++++++++++++++ docker-compose.yml | 5 ++- 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 cvat/apps/lambda_manager/docs/hld.md create mode 100644 cvat/apps/lambda_manager/serializers.py diff --git a/cvat/apps/lambda_manager/docs/hld.md b/cvat/apps/lambda_manager/docs/hld.md new file mode 100644 index 000000000000..15c674a7b456 --- /dev/null +++ b/cvat/apps/lambda_manager/docs/hld.md @@ -0,0 +1,23 @@ +# High-level design for lambda manager + +## Overview + +Users want to use AI to annotate data automatically and semi-automatically. +Thus it is necessary to have a way easily add new AI applications and run +them `online` or `synchronously` for semi-automatic methods and `offline` or +`asynchronously` for automatic methods for many images. A good example of +`online` methods can be deep extreme cut where you need to have an answer +mostly immediately. An example of `offline` methods can be a model from TF +object detection API. + +## REST API + +- GET /api/v1/lambda/functions: get list of functions +- POST /api/v1/lambda/functions: add one more function +- GET /api/v1/lambda/functions/``: get information about the function +- PUT /api/v1/lambda/functions/``: update/replace the function +- DELETE /api/v1/lambda/functions/``: delete a function +- POST /api/v1/lambda/requests: call a function +- GET /api/v1/lambda/requests: get list of requests +- GET /api/v1/lambda/requests/``: get information about the request +- DELETE /api/v1/lambda/requests/``: cancel the request diff --git a/cvat/apps/lambda_manager/serializers.py b/cvat/apps/lambda_manager/serializers.py new file mode 100644 index 000000000000..3a2f7cd6aa3a --- /dev/null +++ b/cvat/apps/lambda_manager/serializers.py @@ -0,0 +1,49 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from rest_framework import serializers +from enum import Enum +from cvat.apps.engine.serializers import (LabeledImageSerializer, + LabeledShapeSerializer, LabeledTrackSerializer) + +class FunctionCall(Enum): + ONLINE = 'online' + OFFLINE = 'offline' + + def __str__(self): + return self.value + + @classmethod + def choices(cls): + return tuple((x.value, x.name) for x in cls) + +class FunctionType(Enum): + PREDICTOR = 'predictor' # image -> objects + TRACKER = 'tracker' # (image, object) -> track of objects + INTERACTOR = 'interactor' # (image, object) -> object + MATCHER = 'matcher' # (object, object) -> correlation coefficient + + def __str__(self): + return self.value + + @classmethod + def choices(cls): + return tuple((x.value, x.name) for x in cls) + + +class FunctionSerializer(serializers.Serializer): + name = serializers.SlugField(max_length=1024) + type = serializers.ChoiceField(FunctionType.choices()) + code = serializers.FileField(write_only=True) + +class RequestSerializer(serializers.Serializer): + function = serializers.SlugField(max_length=1024) + call = serializers.ChoiceField(FunctionCall.choices()) + data = serializers.IntegerField(min_value=0) + start_frame = serializers.IntegerField(required=False, min_value=0) + stop_frame = serializers.IntegerField(required=False, min_value=0) + frame_step = serializers.IntegerField(required=False, min_value=0) + tags = LabeledImageSerializer(many=True, default=[]) + shapes = LabeledShapeSerializer(many=True, default=[]) + tracks = LabeledTrackSerializer(many=True, default=[]) diff --git a/docker-compose.yml b/docker-compose.yml index 4b32341bc54e..4d5501e234e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,7 @@ services: cvat: container_name: cvat - image: cvat + image: cvat/server restart: always depends_on: - cvat_redis @@ -61,6 +61,7 @@ services: cvat_ui: container_name: cvat_ui + image: cvat/ui restart: always build: context: . @@ -95,7 +96,7 @@ services: command: /bin/sh -c "envsubst '$$CVAT_HOST' < /etc/nginx/conf.d/cvat.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" serverless: - container_name: nuclio-dashboard + container_name: nuclio image: quay.io/nuclio/dashboard:stable-amd64 networks: default: From 144f7becc24790843a4141352bc4e6ea8aa0d674 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Wed, 6 May 2020 18:08:04 +0300 Subject: [PATCH 09/98] Initial version of Mask RCNN (without debugging) --- .../auto_segmentation/nuclio/function.yaml | 28 ++--- components/auto_segmentation/nuclio/main.py | 22 ++++ .../auto_segmentation/nuclio/mask_rcnn.py | 105 ++++++++++++++++++ 3 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 components/auto_segmentation/nuclio/main.py create mode 100644 components/auto_segmentation/nuclio/mask_rcnn.py diff --git a/components/auto_segmentation/nuclio/function.yaml b/components/auto_segmentation/nuclio/function.yaml index 257d8617ef31..694b7ef4dabb 100644 --- a/components/auto_segmentation/nuclio/function.yaml +++ b/components/auto_segmentation/nuclio/function.yaml @@ -1,38 +1,32 @@ metadata: - name: dextr + name: tf-maskrcnn namespace: cvat spec: - description: Deep Extreme Cut + description: TensorFlow MASK RCNN runtime: "python:3.6" handler: main:handler eventTimeout: 30s env: - - name: NUCLIO_PYTHON_EXE_PATH - value: /opt/nuclio/python3 + - name: MASK_RCNN_PATH + value: /opt/nuclio/Mask_RCNN build: - image: cvat/dextr - baseImage: openvino/ubuntu18_runtime:2020.2 + image: cvat/tf-maskrcnn + baseImage: tensorflow/tensorflow:2.1.0-py3 directives: - preCopy: - - kind: USER - value: root + postCopy: - kind: WORKDIR value: /opt/nuclio - kind: RUN - value: ln -s /usr/bin/pip3 /usr/bin/pip - - postCopy: + value: apt update && apt install --no-install-recommends -y git curl - kind: RUN - value: curl -O https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip + value: git clone https://github.com/matterport/Mask_RCNN.git - kind: RUN - value: unzip dextr_model_v1.zip + value: curl -L https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5 -o Mask_RCNN/mask_rcnn_coco.h5 - kind: RUN - value: pip3 install -r requirements.txt - - kind: USER - value: openvino + value: pip3 install -r Mask_RCNN/requirements.txt triggers: myHttpTrigger: diff --git a/components/auto_segmentation/nuclio/main.py b/components/auto_segmentation/nuclio/main.py new file mode 100644 index 000000000000..44cb1f1b0a06 --- /dev/null +++ b/components/auto_segmentation/nuclio/main.py @@ -0,0 +1,22 @@ +import json +import base64 +from PIL import Image +import io + +def init_context(context): + context.logger.info("Init context... 0%") + maskrcnn_handler = None + setattr(context.user_data, 'maskrcnn_handler', maskrcnn_handler) + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("call handler") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"])) + image = Image.open(buf) + + objects = context.user_data.maskrcnn_handler.handle(image) + return context.Response(body=json.dumps(objects), + headers={}, + content_type='application/json', + status_code=200) diff --git a/components/auto_segmentation/nuclio/mask_rcnn.py b/components/auto_segmentation/nuclio/mask_rcnn.py new file mode 100644 index 000000000000..72d3abae9e0d --- /dev/null +++ b/components/auto_segmentation/nuclio/mask_rcnn.py @@ -0,0 +1,105 @@ + +# Copyright (C) 2018-2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os +import numpy as np +import sys +import skimage.io +from skimage.measure import find_contours, approximate_polygon + +class MASKRCNN: + def get_labels_by_name(self): + return { "BG": 0, "person": 1, "bicycle": 2, "car": 3, + "motorcycle": 4, "airplane": 5, "bus": 6, "train": 7, "truck": 8, + "boat": 9, "traffic_light": 10, "fire_hydrant": 11, "stop_sign": 12, + "parking_meter": 13, "bench": 14, "bird": 15, "cat": 16, "dog": 17, + "horse": 18, "sheep": 19, "cow": 20, "elephant": 21, "bear": 22, + "zebra": 23, "giraffe": 24, "backpack": 25, "umbrella": 26, + "handbag": 27, "tie": 28, "suitcase": 29, "frisbee": 30, "skis": 31, + "snowboard": 32, "sports_ball": 33, "kite": 34, "baseball_bat": 35, + "baseball_glove": 36, "skateboard": 37, "surfboard": 38, + "tennis_racket": 39, "bottle": 40, "wine_glass": 41, "cup": 42, + "fork": 43, "knife": 44, "spoon": 45, "bowl": 46, "banana": 47, + "apple": 48, "sandwich": 49, "orange": 50, "broccoli": 51, + "carrot": 52, "hot_dog": 53, "pizza": 54, "donut": 55, "cake": 56, + "chair": 57, "couch": 58, "potted_plant": 59, "bed": 60, + "dining_table": 61, "toilet": 62, "tv": 63, "laptop": 64, "mouse": 65, + "remote": 66, "keyboard": 67, "cell_phone": 68, "microwave": 69, + "oven": 70, "toaster": 71, "sink": 72, "refrigerator": 73, "book": 74, + "clock": 75, "vase": 76, "scissors": 77, "teddy_bear": 78, + "hair_drier": 79, "toothbrush": 80 } + + def get_labels_by_id(self): + return {v:k for k,v in self.get_labels_by_id().items()} + + @staticmethod + def _convert_to_segmentation(mask): + contours = find_contours(mask, 0.5) + # only one contour exist in our case + contour = contours[0] + contour = np.flip(contour, axis=1) + # Approximate the contour and reduce the number of points + contour = approximate_polygon(contour, tolerance=2.5) + segmentation = contour.ravel().tolist() + return segmentation + + def __init__(self): + # Root directory of the project + ROOT_DIR = os.environ.get('MASK_RCNN_PATH', '/opt/nuclio/MASKRCNN') + # Import Mask RCNN + sys.path.append(ROOT_DIR) # To find local version of the library + import mrcnn.model as modellib + + # Import COCO config + sys.path.append(os.path.join(ROOT_DIR, "samples/coco/")) # To find local version + import coco + + # Directory to save logs and trained model + MODEL_DIR = os.path.join(ROOT_DIR, "logs") + + # Local path to trained weights file + COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5") + if COCO_MODEL_PATH is None: + raise OSError('Model path env not found in the system.') + + ## CONFIGURATION + + class InferenceConfig(coco.CocoConfig): + # Set batch size to 1 since we'll be running inference on + # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU + GPU_COUNT = 1 + IMAGES_PER_GPU = 1 + + # Print config details + config = InferenceConfig() + config.display() + + ## CREATE MODEL AND LOAD TRAINED WEIGHTS + + # Create model object in inference mode. + self.model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config) + # Load weights trained on MS-COCO + self.model.load_weights(COCO_MODEL_PATH, by_name=True) + + def run(self, image, threshold=0.5): + ## RUN OBJECT DETECTION + results = {} + + # for multiple image detection, "batch size" must be equal to number of images + r = self.model.detect([image], verbose=1) + + r = r[0] + + # "r['rois'][index]" gives bounding box around the object + for index, class_id in enumerate(r['class_ids']): + if r['scores'][index] >= threshold: + mask = r['masks'][:,:,index].astype(np.uint8) + segmentation = _convert_to_segmentation(mask) + if class_id not in results: + results[class_id] = [] + results[class_id].append(segmentation) + + return results + From e0f3aea48fc15d397454b557173e918a6bf2a543 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 9 May 2020 09:41:31 +0300 Subject: [PATCH 10/98] Initial version for faster_rcnn_inception_v2_coco --- components/auto_segmentation/README.md | 38 ----- .../docker-compose.auto_segmentation.yml | 13 -- components/auto_segmentation/install.sh | 13 -- components/openvino/README.md | 45 ----- .../openvino/docker-compose.openvino.yml | 13 -- components/openvino/eula.cfg | 3 - components/openvino/install.sh | 41 ----- .../auto_segmentation/nuclio/function.yaml | 0 .../auto_segmentation/nuclio/main.py | 0 .../auto_segmentation/nuclio/mask_rcnn.py | 0 .../dextr/nuclio/dextr.py | 0 .../dextr/nuclio/function.yaml | 0 .../dextr/nuclio/inference_engine.py | 0 .../dextr/nuclio/input.json | 0 .../dextr/nuclio/main.py | 0 .../dextr/nuclio/python3 | 0 .../dextr/nuclio/requirements.txt | 0 .../dextr/nuclio/run.sh | 0 .../dextr/openfaas/Dockerfile | 0 .../dextr/openfaas/dextr.py | 0 .../dextr/openfaas/dextr.yml | 0 .../dextr/openfaas/index.py | 0 .../dextr/openfaas/index.sh | 0 .../dextr/openfaas/inference_engine.py | 0 .../dextr/openfaas/requirements.txt | 0 .../nuclio/function.yaml | 39 +++++ .../nuclio/inference_engine.py | 52 ++++++ .../nuclio/main.py | 128 ++++++++++++++ .../nuclio/model_loader.py | 69 ++++++++ .../nuclio/python3 | 6 + .../nuclio/README.md | 32 ++++ .../nuclio/function.yaml | 37 ++++ .../nuclio/inference_engine.py | 52 ++++++ .../nuclio/interp.py | 64 +++++++ .../nuclio/main.py | 114 +++++++++++++ .../nuclio/model_loader.py | 69 ++++++++ .../nuclio/python3 | 6 + .../nuclio/requirements.txt | 1 + .../public/yolov-v3-tf/nuclio/README.md | 22 +++ .../public/yolov-v3-tf/nuclio/function.yaml | 37 ++++ .../yolov-v3-tf/nuclio/inference_engine.py | 52 ++++++ .../public/yolov-v3-tf/nuclio/interp.py | 160 ++++++++++++++++++ .../public/yolov-v3-tf/nuclio/main.py | 130 ++++++++++++++ .../public/yolov-v3-tf/nuclio/mapping.json | 84 +++++++++ .../public/yolov-v3-tf/nuclio/model_loader.py | 69 ++++++++ .../public/yolov-v3-tf/nuclio/python3 | 6 + .../yolov-v3-tf/nuclio/requirements.txt | 1 + 47 files changed, 1230 insertions(+), 166 deletions(-) delete mode 100644 components/auto_segmentation/README.md delete mode 100644 components/auto_segmentation/docker-compose.auto_segmentation.yml delete mode 100755 components/auto_segmentation/install.sh delete mode 100644 components/openvino/README.md delete mode 100644 components/openvino/docker-compose.openvino.yml delete mode 100644 components/openvino/eula.cfg delete mode 100755 components/openvino/install.sh rename {components => serverless}/auto_segmentation/nuclio/function.yaml (100%) rename {components => serverless}/auto_segmentation/nuclio/main.py (100%) rename {components => serverless}/auto_segmentation/nuclio/mask_rcnn.py (100%) rename {components => serverless}/dextr/nuclio/dextr.py (100%) rename {components => serverless}/dextr/nuclio/function.yaml (100%) rename {components => serverless}/dextr/nuclio/inference_engine.py (100%) rename {components => serverless}/dextr/nuclio/input.json (100%) rename {components => serverless}/dextr/nuclio/main.py (100%) rename {components => serverless}/dextr/nuclio/python3 (100%) rename {components => serverless}/dextr/nuclio/requirements.txt (100%) rename {components => serverless}/dextr/nuclio/run.sh (100%) rename {components => serverless}/dextr/openfaas/Dockerfile (100%) rename {components => serverless}/dextr/openfaas/dextr.py (100%) rename {components => serverless}/dextr/openfaas/dextr.yml (100%) rename {components => serverless}/dextr/openfaas/index.py (100%) rename {components => serverless}/dextr/openfaas/index.sh (100%) rename {components => serverless}/dextr/openfaas/inference_engine.py (100%) rename {components => serverless}/dextr/openfaas/requirements.txt (100%) create mode 100644 serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml create mode 100644 serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/inference_engine.py create mode 100644 serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py create mode 100644 serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/model_loader.py create mode 100755 serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/python3 create mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/README.md create mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml create mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/inference_engine.py create mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/interp.py create mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py create mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_loader.py create mode 100755 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/python3 create mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/requirements.txt create mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/README.md create mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml create mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/inference_engine.py create mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/interp.py create mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py create mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/mapping.json create mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/model_loader.py create mode 100755 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/python3 create mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/requirements.txt diff --git a/components/auto_segmentation/README.md b/components/auto_segmentation/README.md deleted file mode 100644 index b456857ec4c2..000000000000 --- a/components/auto_segmentation/README.md +++ /dev/null @@ -1,38 +0,0 @@ -## [Keras+Tensorflow Mask R-CNN Segmentation](https://github.com/matterport/Mask_RCNN) - -### What is it? -- This application allows you automatically to segment many various objects on images. -- It's based on Feature Pyramid Network (FPN) and a ResNet101 backbone. - -- It uses a pre-trained model on MS COCO dataset -- It supports next classes (use them in "labels" row): -```python -'BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', -'bus', 'train', 'truck', 'boat', 'traffic light', -'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', -'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', -'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', -'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', -'kite', 'baseball bat', 'baseball glove', 'skateboard', -'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', -'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', -'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', -'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', -'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', -'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', -'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', -'teddy bear', 'hair drier', 'toothbrush'. -``` -- Component adds "Run Auto Segmentation" button into dashboard. - -### Build docker image -```bash -# From project root directory -docker-compose -f docker-compose.yml -f components/auto_segmentation/docker-compose.auto_segmentation.yml build -``` - -### Run docker container -```bash -# From project root directory -docker-compose -f docker-compose.yml -f components/auto_segmentation/docker-compose.auto_segmentation.yml up -d -``` diff --git a/components/auto_segmentation/docker-compose.auto_segmentation.yml b/components/auto_segmentation/docker-compose.auto_segmentation.yml deleted file mode 100644 index 1d9763cdf6cd..000000000000 --- a/components/auto_segmentation/docker-compose.auto_segmentation.yml +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -version: "2.3" - -services: - cvat: - build: - context: . - args: - AUTO_SEGMENTATION: "yes" diff --git a/components/auto_segmentation/install.sh b/components/auto_segmentation/install.sh deleted file mode 100755 index d6881a68150d..000000000000 --- a/components/auto_segmentation/install.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -# - -set -e - -MASK_RCNN_URL=https://github.com/matterport/Mask_RCNN - -cd ${HOME} && \ -git clone ${MASK_RCNN_URL}.git && \ -curl -L ${MASK_RCNN_URL}/releases/download/v2.0/mask_rcnn_coco.h5 -o Mask_RCNN/mask_rcnn_coco.h5 - -# TODO remove useless files -# tensorflow and Keras are installed globally diff --git a/components/openvino/README.md b/components/openvino/README.md deleted file mode 100644 index 3b3a123b304b..000000000000 --- a/components/openvino/README.md +++ /dev/null @@ -1,45 +0,0 @@ -## [Intel OpenVINO toolkit](https://software.intel.com/en-us/openvino-toolkit) - -### Requirements - -* Intel Core with 6th generation and higher or Intel Xeon CPUs. - -### Preparation - -- Download the latest [OpenVINO toolkit](https://software.intel.com/en-us/openvino-toolkit) .tgz installer -(offline or online) for Ubuntu platforms. Note that OpenVINO does not maintain forward compatability between -Intermediate Representations (IRs), so the version of OpenVINO in CVAT and the version used to translate the -models needs to be the same. -- Put downloaded file into ```cvat/components/openvino```. -- Accept EULA in the `cvat/components/openvino/eula.cfg` file. - -### Build docker image -```bash -# From project root directory -docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml build -``` - -### Run docker container -```bash -# From project root directory -docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml up -d -``` - -You should be able to login and see the web interface for CVAT now, complete with the new "Model Manager" button. - -### OpenVINO Models - -Clone the [Open Model Zoo](https://github.com/opencv/open_model_zoo). `$ git clone https://github.com/opencv/open_model_zoo.git` - -Install the appropriate libraries. Currently that command would be `$ pip install -r open_model_zoo/tools/downloader/requirements.in` - -Download the models using `downloader.py` file in `open_model_zoo/tools/downloader/`. -The `--name` command can be used to specify specific models. -The `--print_all` command can print all the available models. -Specific models that are already integrated into Cvat can be found [here](https://github.com/opencv/cvat/tree/develop/utils/open_model_zoo). - -From the web user interface in CVAT, upload the models using the model manager. -You'll need to include the xml and bin file from the model downloader. -You'll need to include the python and JSON files from scratch or by using the ones in the CVAT libary. -See [here](https://github.com/opencv/cvat/tree/develop/cvat/apps/auto_annotation) for instructions for creating custom -python and JSON files. diff --git a/components/openvino/docker-compose.openvino.yml b/components/openvino/docker-compose.openvino.yml deleted file mode 100644 index 3805f1b810df..000000000000 --- a/components/openvino/docker-compose.openvino.yml +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -version: "2.3" - -services: - cvat: - build: - context: . - args: - OPENVINO_TOOLKIT: "yes" diff --git a/components/openvino/eula.cfg b/components/openvino/eula.cfg deleted file mode 100644 index 7c34e8fe1d62..000000000000 --- a/components/openvino/eula.cfg +++ /dev/null @@ -1,3 +0,0 @@ -# Accept actual EULA from openvino installation archive. Valid values are: {accept, decline} -ACCEPT_EULA=accept - diff --git a/components/openvino/install.sh b/components/openvino/install.sh deleted file mode 100755 index 159ff32d1b43..000000000000 --- a/components/openvino/install.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -set -e - -if [[ `lscpu | grep -o "GenuineIntel"` != "GenuineIntel" ]]; then - echo "OpenVINO supports only Intel CPUs" - exit 1 -fi - -if [[ `lscpu | grep -o "sse4" | head -1` != "sse4" ]] && [[ `lscpu | grep -o "avx2" | head -1` != "avx2" ]]; then - echo "OpenVINO expects your CPU to support SSE4 or AVX2 instructions" - exit 1 -fi - - -cd /tmp/components/openvino - -tar -xzf `ls | grep "openvino_toolkit"` -cd `ls -d */ | grep "openvino_toolkit"` - -apt-get update && apt-get --no-install-recommends install -y sudo cpio && \ - if [ -f "install_cv_sdk_dependencies.sh" ]; then ./install_cv_sdk_dependencies.sh; \ - else ./install_openvino_dependencies.sh; fi && SUDO_FORCE_REMOVE=yes apt-get remove -y sudo - - -cat ../eula.cfg >> silent.cfg -./install.sh -s silent.cfg - -cd /tmp/components && rm openvino -r - -if [ -f "/opt/intel/computer_vision_sdk/bin/setupvars.sh" ]; then - echo "source /opt/intel/computer_vision_sdk/bin/setupvars.sh" >> ${HOME}/.bashrc; - echo -e '\nexport IE_PLUGINS_PATH=${IE_PLUGINS_PATH}' >> /opt/intel/computer_vision_sdk/bin/setupvars.sh; -else - echo "source /opt/intel/openvino/bin/setupvars.sh" >> ${HOME}/.bashrc; - echo -e '\nexport IE_PLUGINS_PATH=${IE_PLUGINS_PATH}' >> /opt/intel/openvino/bin/setupvars.sh; -fi diff --git a/components/auto_segmentation/nuclio/function.yaml b/serverless/auto_segmentation/nuclio/function.yaml similarity index 100% rename from components/auto_segmentation/nuclio/function.yaml rename to serverless/auto_segmentation/nuclio/function.yaml diff --git a/components/auto_segmentation/nuclio/main.py b/serverless/auto_segmentation/nuclio/main.py similarity index 100% rename from components/auto_segmentation/nuclio/main.py rename to serverless/auto_segmentation/nuclio/main.py diff --git a/components/auto_segmentation/nuclio/mask_rcnn.py b/serverless/auto_segmentation/nuclio/mask_rcnn.py similarity index 100% rename from components/auto_segmentation/nuclio/mask_rcnn.py rename to serverless/auto_segmentation/nuclio/mask_rcnn.py diff --git a/components/dextr/nuclio/dextr.py b/serverless/dextr/nuclio/dextr.py similarity index 100% rename from components/dextr/nuclio/dextr.py rename to serverless/dextr/nuclio/dextr.py diff --git a/components/dextr/nuclio/function.yaml b/serverless/dextr/nuclio/function.yaml similarity index 100% rename from components/dextr/nuclio/function.yaml rename to serverless/dextr/nuclio/function.yaml diff --git a/components/dextr/nuclio/inference_engine.py b/serverless/dextr/nuclio/inference_engine.py similarity index 100% rename from components/dextr/nuclio/inference_engine.py rename to serverless/dextr/nuclio/inference_engine.py diff --git a/components/dextr/nuclio/input.json b/serverless/dextr/nuclio/input.json similarity index 100% rename from components/dextr/nuclio/input.json rename to serverless/dextr/nuclio/input.json diff --git a/components/dextr/nuclio/main.py b/serverless/dextr/nuclio/main.py similarity index 100% rename from components/dextr/nuclio/main.py rename to serverless/dextr/nuclio/main.py diff --git a/components/dextr/nuclio/python3 b/serverless/dextr/nuclio/python3 similarity index 100% rename from components/dextr/nuclio/python3 rename to serverless/dextr/nuclio/python3 diff --git a/components/dextr/nuclio/requirements.txt b/serverless/dextr/nuclio/requirements.txt similarity index 100% rename from components/dextr/nuclio/requirements.txt rename to serverless/dextr/nuclio/requirements.txt diff --git a/components/dextr/nuclio/run.sh b/serverless/dextr/nuclio/run.sh similarity index 100% rename from components/dextr/nuclio/run.sh rename to serverless/dextr/nuclio/run.sh diff --git a/components/dextr/openfaas/Dockerfile b/serverless/dextr/openfaas/Dockerfile similarity index 100% rename from components/dextr/openfaas/Dockerfile rename to serverless/dextr/openfaas/Dockerfile diff --git a/components/dextr/openfaas/dextr.py b/serverless/dextr/openfaas/dextr.py similarity index 100% rename from components/dextr/openfaas/dextr.py rename to serverless/dextr/openfaas/dextr.py diff --git a/components/dextr/openfaas/dextr.yml b/serverless/dextr/openfaas/dextr.yml similarity index 100% rename from components/dextr/openfaas/dextr.yml rename to serverless/dextr/openfaas/dextr.yml diff --git a/components/dextr/openfaas/index.py b/serverless/dextr/openfaas/index.py similarity index 100% rename from components/dextr/openfaas/index.py rename to serverless/dextr/openfaas/index.py diff --git a/components/dextr/openfaas/index.sh b/serverless/dextr/openfaas/index.sh similarity index 100% rename from components/dextr/openfaas/index.sh rename to serverless/dextr/openfaas/index.sh diff --git a/components/dextr/openfaas/inference_engine.py b/serverless/dextr/openfaas/inference_engine.py similarity index 100% rename from components/dextr/openfaas/inference_engine.py rename to serverless/dextr/openfaas/inference_engine.py diff --git a/components/dextr/openfaas/requirements.txt b/serverless/dextr/openfaas/requirements.txt similarity index 100% rename from components/dextr/openfaas/requirements.txt rename to serverless/dextr/openfaas/requirements.txt diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml new file mode 100644 index 000000000000..f2ab2c76c2e9 --- /dev/null +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -0,0 +1,39 @@ +metadata: + name: omz.public.faster_rcnn_inception_v2_coco + namespace: cvat + +spec: + description: Faster RCNN inception v2 COCO from Intel Open Model Zoo + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/python3 + + build: + image: cvat/open_model_zoo/public/faster_rcnn_inception_v2_coco + baseImage: openvino/ubuntu18_dev:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + + postCopy: + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name faster_rcnn_inception_v2_coco -o /opt/nuclio/open_model_zoo + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name faster_rcnn_inception_v2_coco --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo + - kind: USER + value: openvino + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/inference_engine.py b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/inference_engine.py new file mode 100644 index 000000000000..226af0b1416e --- /dev/null +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/inference_engine.py @@ -0,0 +1,52 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version + +import subprocess +import os +import platform + +_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) + +def _check_instruction(instruction): + return instruction == str.strip( + subprocess.check_output( + 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True + ).decode('utf-8') + ) + + +def make_plugin_or_core(): + version = get_version() + use_core_openvino = False + try: + major, minor, _ = [int(x) for x in version.split('.')] + if major >= 2 and minor >= 1: + use_core_openvino = True + except Exception: + pass + + if use_core_openvino: + ie = IECore() + return ie + + if _IE_PLUGINS_PATH is None: + raise OSError('Inference engine plugin path env not found in the system.') + + plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH]) + if (_check_instruction('avx2')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so')) + elif (_check_instruction('sse4')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so')) + elif platform.system() == 'Darwin': + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension.dylib')) + else: + raise Exception('Inference engine requires a support of avx2 or sse4.') + + return plugin + + +def make_network(model, weights): + return IENetwork(model = model, weights = weights) diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py new file mode 100644 index 000000000000..b00acc9f2101 --- /dev/null +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py @@ -0,0 +1,128 @@ +import json +import base64 +from PIL import Image +import io +from model_loader import ModelLoader +import sys +import time + +def init_context(context): + context.logger.info("Init context... 0%") + model_xml = "/opt/nuclio/open_model_zoo/public/faster_rcnn_inception_v2_coco/FP32/faster_rcnn_inception_v2_coco.xml" + model_bin = "/opt/nuclio/open_model_zoo/public/faster_rcnn_inception_v2_coco/FP32/faster_rcnn_inception_v2_coco.bin" + model_handler = ModelLoader(model_xml, model_bin) + setattr(context.user_data, 'model_handler', model_handler) + labels = { + 1: "person", + 2: "bicycle", + 3: "car", + 4: "motorcycle", + 5: "airplane", + 6: "bus", + 7: "train", + 8: "truck", + 9: "boat", + 10: "traffic_light", + 11: "fire_hydrant", + 13: "stop_sign", + 14: "parking_meter", + 15: "bench", + 16: "bird", + 17: "cat", + 18: "dog", + 19: "horse", + 20: "sheep", + 21: "cow", + 22: "elephant", + 23: "bear", + 24: "zebra", + 25: "giraffe", + 27: "backpack", + 28: "umbrella", + 31: "handbag", + 32: "tie", + 33: "suitcase", + 34: "frisbee", + 35: "skis", + 36: "snowboard", + 37: "sports_ball", + 38: "kite", + 39: "baseball_bat", + 40: "baseball_glove", + 41: "skateboard", + 42: "surfboard", + 43: "tennis_racket", + 44: "bottle", + 46: "wine_glass", + 47: "cup", + 48: "fork", + 49: "knife", + 50: "spoon", + 51: "bowl", + 52: "banana", + 53: "apple", + 54: "sandwich", + 55: "orange", + 56: "broccoli", + 57: "carrot", + 58: "hot_dog", + 59: "pizza", + 60: "donut", + 61: "cake", + 62: "chair", + 63: "couch", + 64: "potted_plant", + 65: "bed", + 67: "dining_table", + 70: "toilet", + 72: "tv", + 73: "laptop", + 74: "mouse", + 75: "remote", + 76: "keyboard", + 77: "cell_phone", + 78: "microwave", + 79: "oven", + 80: "toaster", + 81: "sink", + 83: "refrigerator", + 84: "book", + 85: "clock", + 86: "vase", + 87: "scissors", + 88: "teddy_bear", + 89: "hair_drier", + 90: "toothbrush" + } + setattr(context.user_data, "labels", labels) + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run faster_rcnn_inception_v2_coco model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"])) + threshold = float(data.get("threshold", 0.5)) + image = Image.open(buf) + + output_layer = context.user_data.model_handler.infer(image) + + results = [] + prediction = output_layer[0][0] + for obj in prediction: + obj_class = context.labels[int(obj[1])] + obj_value = obj[2] + if obj_value >= threshold: + xtl = obj[3] * image.width + ytl = obj[4] * image.height + xbr = obj[5] * image.width + ybr = obj[6] * image.height + + results.append({ + "label": obj_class, + "points": [xtl, ytl, xbr, ybr], + "type": "rectangle", + "attributes": {} + }) + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/model_loader.py b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/model_loader.py new file mode 100644 index 000000000000..c70baf6ea03b --- /dev/null +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/model_loader.py @@ -0,0 +1,69 @@ + +# Copyright (C) 2018-2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import cv2 +import numpy as np + +from inference_engine import make_plugin_or_core, make_network + +class ModelLoader: + def __init__(self, model, weights): + self._model = model + self._weights = weights + + core_or_plugin = make_plugin_or_core() + network = make_network(self._model, self._weights) + + if getattr(core_or_plugin, 'get_supported_layers', False): + supported_layers = core_or_plugin.get_supported_layers(network) + not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] + if len(not_supported_layers) != 0: + raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}". + format(core_or_plugin.device, ", ".join(not_supported_layers))) + + iter_inputs = iter(network.inputs) + self._input_blob_name = next(iter_inputs) + self._input_info_name = '' + self._output_blob_name = next(iter(network.outputs)) + + self._require_image_info = False + + info_names = ('image_info', 'im_info') + + # NOTE: handeling for the inclusion of `image_info` in OpenVino2019 + if any(s in network.inputs for s in info_names): + self._require_image_info = True + self._input_info_name = set(network.inputs).intersection(info_names) + self._input_info_name = self._input_info_name.pop() + if self._input_blob_name in info_names: + self._input_blob_name = next(iter_inputs) + + if getattr(core_or_plugin, 'load_network', False): + self._net = core_or_plugin.load_network(network, + "CPU", + num_requests=2) + else: + self._net = core_or_plugin.load(network=network, num_requests=2) + input_type = network.inputs[self._input_blob_name] + self._input_layout = input_type if isinstance(input_type, list) else input_type.shape + + def infer(self, image): + _, _, h, w = self._input_layout + in_frame = image if image.shape[:-1] == (h, w) else cv2.resize(image, (w, h)) + in_frame = in_frame.transpose((2, 0, 1)) # Change data layout from HWC to CHW + inputs = {self._input_blob_name: in_frame} + if self._require_image_info: + info = np.zeros([1, 3]) + info[0, 0] = h + info[0, 1] = w + # frame number + info[0, 2] = 1 + inputs[self._input_info_name] = info + + results = self._net.infer(inputs) + if len(results) == 1: + return results[self._output_blob_name].copy() + else: + return results.copy() diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/python3 b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/python3 new file mode 100755 index 000000000000..b80f584712d7 --- /dev/null +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/python3 @@ -0,0 +1,6 @@ +#!/bin/bash + +args=$@ + +. /opt/intel/openvino/bin/setupvars.sh +/usr/bin/python3 $args diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/README.md b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/README.md new file mode 100644 index 000000000000..be7a82121bf0 --- /dev/null +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/README.md @@ -0,0 +1,32 @@ +# mask_rcnn_inception_resnet_v2_atrous_coco + +## Use Case and High-Level Description + +Mask R-CNN Inception Resnet V2 Atrous is trained on COCO dataset and used for object instance segmentation. +For details, see a [paper](https://arxiv.org/pdf/1703.06870.pdf). + +## Specification + +| Metric | Value | +|---------------------------------|-------------------------------------------| +| Type | Instance segmentation | +| GFlops | 675.314 | +| MParams | 92.368 | +| Source framework | TensorFlow\* | + +## Legal Information + +[https://raw.githubusercontent.com/tensorflow/models/master/LICENSE]() + +## OpenVINO Conversion Notes + +In order to convert the code into the openvino format, please see the [following link](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_Object_Detection_API_Models.html#mask_r_cnn_topologies). + +The conversion command from the command line prompt will look something like the following. + +```shell +$ python /opt/intel/openvino/deployment_tools/model_optimizer/mo_tf.py \ + --input_model /path/to/frozen_inference_graph.pb \ + --tensorflow_use_custom_operations_config /opt/intel/openvino/deployment_tools/model_optimizer/extensions/front/tf/mask_rcnn_support.json \ + --tensorflow_object_detection_api_pipeline_config /path/to/pipeline.config +``` diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml new file mode 100644 index 000000000000..8b17196d6ba3 --- /dev/null +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -0,0 +1,37 @@ +metadata: + name: open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco + namespace: cvat + +spec: + description: Mask RCNN inception resnet v2 from Intel Open Model Zoo + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/python3 + + build: + image: cvat/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco + baseImage: openvino/ubuntu18_runtime:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + + postCopy: + - kind: RUN + value: pip3 install -r requirements.txt + - kind: USER + value: openvino + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/inference_engine.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/inference_engine.py new file mode 100644 index 000000000000..226af0b1416e --- /dev/null +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/inference_engine.py @@ -0,0 +1,52 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version + +import subprocess +import os +import platform + +_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) + +def _check_instruction(instruction): + return instruction == str.strip( + subprocess.check_output( + 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True + ).decode('utf-8') + ) + + +def make_plugin_or_core(): + version = get_version() + use_core_openvino = False + try: + major, minor, _ = [int(x) for x in version.split('.')] + if major >= 2 and minor >= 1: + use_core_openvino = True + except Exception: + pass + + if use_core_openvino: + ie = IECore() + return ie + + if _IE_PLUGINS_PATH is None: + raise OSError('Inference engine plugin path env not found in the system.') + + plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH]) + if (_check_instruction('avx2')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so')) + elif (_check_instruction('sse4')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so')) + elif platform.system() == 'Darwin': + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension.dylib')) + else: + raise Exception('Inference engine requires a support of avx2 or sse4.') + + return plugin + + +def make_network(model, weights): + return IENetwork(model = model, weights = weights) diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/interp.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/interp.py new file mode 100644 index 000000000000..6625a8349330 --- /dev/null +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/interp.py @@ -0,0 +1,64 @@ +import numpy as np +import cv2 +from skimage.measure import approximate_polygon, find_contours + + +MASK_THRESHOLD = .5 +PROBABILITY_THRESHOLD = 0.2 + + +# Ref: https://software.intel.com/en-us/forums/computer-vision/topic/804895 +def segm_postprocess(box: list, raw_cls_mask, im_h, im_w, threshold): + ymin, xmin, ymax, xmax = box + + width = int(abs(xmax - xmin)) + height = int(abs(ymax - ymin)) + + result = np.zeros((im_h, im_w), dtype=np.uint8) + resized_mask = cv2.resize(raw_cls_mask, dsize=(height, width), interpolation=cv2.INTER_CUBIC) + + # extract the ROI of the image + ymin = int(round(ymin)) + xmin = int(round(xmin)) + ymax = ymin + height + xmax = xmin + width + result[xmin:xmax, ymin:ymax] = (resized_mask>threshold).astype(np.uint8) * 255 + + return result + + +for detection in detections: + frame_number = detection['frame_id'] + height = detection['frame_height'] + width = detection['frame_width'] + detection = detection['detections'] + + masks = detection['masks'] + boxes = detection['reshape_do_2d'] + + for index, box in enumerate(boxes): + label = int(box[1]) + obj_value = box[2] + if obj_value >= PROBABILITY_THRESHOLD: + x = box[3] * width + y = box[4] * height + right = box[5] * width + bottom = box[6] * height + mask = masks[index][label - 1] + + mask = segm_postprocess((x, y, right, bottom), + mask, + height, + width, + MASK_THRESHOLD) + + contours = find_contours(mask, MASK_THRESHOLD) + contour = contours[0] + contour = np.flip(contour, axis=1) + contour = approximate_polygon(contour, tolerance=2.5) + segmentation = contour.tolist() + + + # NOTE: if you want to see the boxes, uncomment next line + # results.add_box(x, y, right, bottom, label, frame_number) + results.add_polygon(segmentation, label, frame_number) diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py new file mode 100644 index 000000000000..140773753f2f --- /dev/null +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py @@ -0,0 +1,114 @@ +import json +import base64 +from PIL import Image +import io +from .model_loader import ModelLoader +import sys +import time + +def init_context(context): + context.logger.info("Init context... 0%") + model_xml = "./fmask_rcnn_inception_resnet_v2_atrous_coco.xml" + model_bin = "./mask_rcnn_inception_resnet_v2_atrous_coco.bin" + model_handler = ModelLoader(model_xml, model_bin) + setattr(context.user_data, 'model_handler', model_handler) + labels = { + 1: "person", + 2: "bicycle", + 3: "car", + 4: "motorcycle", + 5: "airplane", + 6: "bus", + 7: "train", + 8: "truck", + 9: "boat", + 10: "traffic_light", + 11: "fire_hydrant", + 13: "stop_sign", + 14: "parking_meter", + 15: "bench", + 16: "bird", + 17: "cat", + 18: "dog", + 19: "horse", + 20: "sheep", + 21: "cow", + 22: "elephant", + 23: "bear", + 24: "zebra", + 25: "giraffe", + 27: "backpack", + 28: "umbrella", + 31: "handbag", + 32: "tie", + 33: "suitcase", + 34: "frisbee", + 35: "skis", + 36: "snowboard", + 37: "sports_ball", + 38: "kite", + 39: "baseball_bat", + 40: "baseball_glove", + 41: "skateboard", + 42: "surfboard", + 43: "tennis_racket", + 44: "bottle", + 46: "wine_glass", + 47: "cup", + 48: "fork", + 49: "knife", + 50: "spoon", + 51: "bowl", + 52: "banana", + 53: "apple", + 54: "sandwich", + 55: "orange", + 56: "broccoli", + 57: "carrot", + 58: "hot_dog", + 59: "pizza", + 60: "donut", + 61: "cake", + 62: "chair", + 63: "couch", + 64: "potted_plant", + 65: "bed", + 67: "dining_table", + 70: "toilet", + 72: "tv", + 73: "laptop", + 74: "mouse", + 75: "remote", + 76: "keyboard", + 77: "cell_phone", + 78: "microwave", + 79: "oven", + 80: "toaster", + 81: "sink", + 83: "refrigerator", + 84: "book", + 85: "clock", + 86: "vase", + 87: "scissors", + 88: "teddy_bear", + 89: "hair_drier", + 90: "toothbrush" + } + setattr(context.user_data, "labels", labels) + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run mask_rcnn_inception_resnet_v2_atrous_coco model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"])) + threshold = float(data.get("threshold", 0.5)) + image = Image.open(buf) + + output_layer = context.user_data.model_handler.infer(image) + + results = [] + + return context.Response(body=json.dumps(results), + headers={}, + content_type='application/json', + status_code=200) diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_loader.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_loader.py new file mode 100644 index 000000000000..108febbaa547 --- /dev/null +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_loader.py @@ -0,0 +1,69 @@ + +# Copyright (C) 2018-2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import cv2 +import numpy as np + +from .inference_engine import make_plugin_or_core, make_network + +class ModelLoader: + def __init__(self, model, weights): + self._model = model + self._weights = weights + + core_or_plugin = make_plugin_or_core() + network = make_network(self._model, self._weights) + + if getattr(core_or_plugin, 'get_supported_layers', False): + supported_layers = core_or_plugin.get_supported_layers(network) + not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] + if len(not_supported_layers) != 0: + raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}". + format(core_or_plugin.device, ", ".join(not_supported_layers))) + + iter_inputs = iter(network.inputs) + self._input_blob_name = next(iter_inputs) + self._input_info_name = '' + self._output_blob_name = next(iter(network.outputs)) + + self._require_image_info = False + + info_names = ('image_info', 'im_info') + + # NOTE: handeling for the inclusion of `image_info` in OpenVino2019 + if any(s in network.inputs for s in info_names): + self._require_image_info = True + self._input_info_name = set(network.inputs).intersection(info_names) + self._input_info_name = self._input_info_name.pop() + if self._input_blob_name in info_names: + self._input_blob_name = next(iter_inputs) + + if getattr(core_or_plugin, 'load_network', False): + self._net = core_or_plugin.load_network(network, + "CPU", + num_requests=2) + else: + self._net = core_or_plugin.load(network=network, num_requests=2) + input_type = network.inputs[self._input_blob_name] + self._input_layout = input_type if isinstance(input_type, list) else input_type.shape + + def infer(self, image): + _, _, h, w = self._input_layout + in_frame = image if image.shape[:-1] == (h, w) else cv2.resize(image, (w, h)) + in_frame = in_frame.transpose((2, 0, 1)) # Change data layout from HWC to CHW + inputs = {self._input_blob_name: in_frame} + if self._require_image_info: + info = np.zeros([1, 3]) + info[0, 0] = h + info[0, 1] = w + # frame number + info[0, 2] = 1 + inputs[self._input_info_name] = info + + results = self._net.infer(inputs) + if len(results) == 1: + return results[self._output_blob_name].copy() + else: + return results.copy() diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/python3 b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/python3 new file mode 100755 index 000000000000..b80f584712d7 --- /dev/null +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/python3 @@ -0,0 +1,6 @@ +#!/bin/bash + +args=$@ + +. /opt/intel/openvino/bin/setupvars.sh +/usr/bin/python3 $args diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/requirements.txt b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/requirements.txt new file mode 100644 index 000000000000..5873a2224bf9 --- /dev/null +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/requirements.txt @@ -0,0 +1 @@ +Pillow \ No newline at end of file diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/README.md b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/README.md new file mode 100644 index 000000000000..2e47953cb3f8 --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/README.md @@ -0,0 +1,22 @@ +# Object Detection YOLO V3 Python Demo, Async API Performance Showcase + +See [these instructions][1] for converting the yolo weights to the OpenVino format. + +As of OpenVINO 2019 R3, only tensorflow 1.13 and NetworkX 2.3. +These can be explicitly installed using the following command. + +```bash +python3 -m pip install tensorflow==1.13 networkx==2.3 +``` + + +Additionally, at the time of writing, the model optimizer required an input shape. + +``` bash +python3 mo_tf.py \ + --input_model /path/to/yolo_v3.pb \ + --tensorflow_use_custom_operations_config $MO_ROOT/extensions/front/tf/yolo_v3.json \ + --input_shape [1,416,416,3] +``` + +[1]: https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_YOLO_From_Tensorflow.html diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml new file mode 100644 index 000000000000..0b012863439c --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml @@ -0,0 +1,37 @@ +metadata: + name: open_model_zoo/public/faster_rcnn_inception_v2_coco + namespace: cvat + +spec: + description: Faster RCNN inception v2 COCO from Intel Open Model Zoo + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/python3 + + build: + image: cvat/open_model_zoo/public/faster_rcnn_inception_v2_coco + baseImage: openvino/ubuntu18_runtime:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + + postCopy: + - kind: RUN + value: pip3 install -r requirements.txt + - kind: USER + value: openvino + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/inference_engine.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/inference_engine.py new file mode 100644 index 000000000000..226af0b1416e --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/inference_engine.py @@ -0,0 +1,52 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version + +import subprocess +import os +import platform + +_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) + +def _check_instruction(instruction): + return instruction == str.strip( + subprocess.check_output( + 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True + ).decode('utf-8') + ) + + +def make_plugin_or_core(): + version = get_version() + use_core_openvino = False + try: + major, minor, _ = [int(x) for x in version.split('.')] + if major >= 2 and minor >= 1: + use_core_openvino = True + except Exception: + pass + + if use_core_openvino: + ie = IECore() + return ie + + if _IE_PLUGINS_PATH is None: + raise OSError('Inference engine plugin path env not found in the system.') + + plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH]) + if (_check_instruction('avx2')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so')) + elif (_check_instruction('sse4')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so')) + elif platform.system() == 'Darwin': + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension.dylib')) + else: + raise Exception('Inference engine requires a support of avx2 or sse4.') + + return plugin + + +def make_network(model, weights): + return IENetwork(model = model, weights = weights) diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/interp.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/interp.py new file mode 100644 index 000000000000..4c76c85448d0 --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/interp.py @@ -0,0 +1,160 @@ +from math import exp + + +class Parser: + IOU_THRESHOLD = 0.4 + PROB_THRESHOLD = 0.5 + + def __init__(self): + self.objects = [] + + def scale_bbox(self, x, y, h, w, class_id, confidence, h_scale, w_scale): + xmin = int((x - w / 2) * w_scale) + ymin = int((y - h / 2) * h_scale) + xmax = int(xmin + w * w_scale) + ymax = int(ymin + h * h_scale) + + return dict(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, class_id=class_id, confidence=confidence) + + def entry_index(self, side, coord, classes, location, entry): + side_power_2 = side ** 2 + n = location // side_power_2 + loc = location % side_power_2 + return int(side_power_2 * (n * (coord + classes + 1) + entry) + loc) + + def intersection_over_union(self, box_1, box_2): + width_of_overlap_area = min(box_1['xmax'], box_2['xmax']) - max(box_1['xmin'], box_2['xmin']) + height_of_overlap_area = min(box_1['ymax'], box_2['ymax']) - max(box_1['ymin'], box_2['ymin']) + if width_of_overlap_area < 0 or height_of_overlap_area < 0: + area_of_overlap = 0 + else: + area_of_overlap = width_of_overlap_area * height_of_overlap_area + box_1_area = (box_1['ymax'] - box_1['ymin']) * (box_1['xmax'] - box_1['xmin']) + box_2_area = (box_2['ymax'] - box_2['ymin']) * (box_2['xmax'] - box_2['xmin']) + area_of_union = box_1_area + box_2_area - area_of_overlap + if area_of_union == 0: + return 0 + return area_of_overlap / area_of_union + + + def sort_objects(self): + self.objects = sorted(self.objects, key=lambda obj : obj['confidence'], reverse=True) + + for i in range(len(self.objects)): + if self.objects[i]['confidence'] == 0: + continue + for j in range(i + 1, len(self.objects)): + if self.intersection_over_union(self.objects[i], self.objects[j]) > self.IOU_THRESHOLD: + self.objects[j]['confidence'] = 0 + + def parse_yolo_region(self, blob: 'np.ndarray', original_shape: list, params: dict) -> list: + + # YOLO magic numbers + # See: https://github.com/opencv/open_model_zoo/blob/acf297c73db8cb3f68791ae1fad4a7cc4a6039e5/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L61 + num = 3 + coords = 4 + classes = 80 + # ----------------- + + _, _, out_blob_h, out_blob_w = blob.shape + assert out_blob_w == out_blob_h, "Invalid size of output blob. It sould be in NCHW layout and height should " \ + "be equal to width. Current height = {}, current width = {}" \ + "".format(out_blob_h, out_blob_w) + + # ------ Extracting layer parameters -- + orig_im_h, orig_im_w = original_shape + predictions = blob.flatten() + side_square = params['side'] * params['side'] + + # ------ Parsing YOLO Region output -- + for i in range(side_square): + row = i // params['side'] + col = i % params['side'] + for n in range(num): + # -----entry index calcs------ + obj_index = self.entry_index(params['side'], coords, classes, n * side_square + i, coords) + scale = predictions[obj_index] + if scale < self.PROB_THRESHOLD: + continue + box_index = self.entry_index(params['side'], coords, classes, n * side_square + i, 0) + + # Network produces location predictions in absolute coordinates of feature maps. + # Scale it to relative coordinates. + x = (col + predictions[box_index + 0 * side_square]) / params['side'] * 416 + y = (row + predictions[box_index + 1 * side_square]) / params['side'] * 416 + # Value for exp is very big number in some cases so following construction is using here + try: + h_exp = exp(predictions[box_index + 3 * side_square]) + w_exp = exp(predictions[box_index + 2 * side_square]) + except OverflowError: + continue + + w = w_exp * params['anchors'][2 * n] + h = h_exp * params['anchors'][2 * n + 1] + + for j in range(classes): + class_index = self.entry_index(params['side'], coords, classes, n * side_square + i, + coords + 1 + j) + confidence = scale * predictions[class_index] + if confidence < self.PROB_THRESHOLD: + continue + + self.objects.append(self.scale_bbox(x=x, + y=y, + h=h, + w=w, + class_id=j, + confidence=confidence, + h_scale=(orig_im_h/416), + w_scale=(orig_im_w/416))) + + +for detection in detections: + frame_number = detection['frame_id'] + height = detection['frame_height'] + width = detection['frame_width'] + detection = detection['detections'] + + original_shape = (height, width) + + # https://github.com/opencv/open_model_zoo/blob/master/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L72 + anchors = [10,13,16,30,33,23,30,61,62,45,59,119,116,90,156,198,373,326] + conv_6 = {'side': 13, 'mask': [6,7,8]} + conv_14 = {'side': 26, 'mask': [3,4,5]} + conv_22 = {'side': 52, 'mask': [0,1,2]} + + yolo_params = {'detector/yolo-v3/Conv_6/BiasAdd/YoloRegion': conv_6, + 'detector/yolo-v3/Conv_14/BiasAdd/YoloRegion': conv_14, + 'detector/yolo-v3/Conv_22/BiasAdd/YoloRegion': conv_22} + + for conv_net in yolo_params.values(): + mask = conv_net['mask'] + masked_anchors = [] + for idx in mask: + masked_anchors += [anchors[idx * 2], anchors[idx * 2 + 1]] + + conv_net['anchors'] = masked_anchors + + parser = Parser() + + for name, blob in detection.items(): + parser.parse_yolo_region(blob, original_shape, yolo_params[name]) + + parser.sort_objects() + + objects = [] + for obj in parser.objects: + if obj['confidence'] >= parser.PROB_THRESHOLD: + label = obj['class_id'] + xmin = obj['xmin'] + xmax = obj['xmax'] + ymin = obj['ymin'] + ymax = obj['ymax'] + + # Enforcing extra checks for bounding box coordinates + xmin = max(0,xmin) + ymin = max(0,ymin) + xmax = min(xmax,width) + ymax = min(ymax,height) + + results.add_box(xmin, ymin, xmax, ymax, label, frame_number) diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py new file mode 100644 index 000000000000..5b4c754a0598 --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py @@ -0,0 +1,130 @@ +import json +import base64 +from PIL import Image +import io +from .model_loader import ModelLoader +import sys +import time + +def init_context(context): + context.logger.info("Init context... 0%") + model_xml = "./faster_rcnn_inception_v2_coco.xml" + model_bin = "./faster_rcnn_inception_v2_coco.bin" + model_handler = ModelLoader(model_xml, model_bin) + setattr(context.user_data, 'model_handler', model_handler) + labels = { + 1: "person", + 2: "bicycle", + 3: "car", + 4: "motorcycle", + 5: "airplane", + 6: "bus", + 7: "train", + 8: "truck", + 9: "boat", + 10: "traffic_light", + 11: "fire_hydrant", + 13: "stop_sign", + 14: "parking_meter", + 15: "bench", + 16: "bird", + 17: "cat", + 18: "dog", + 19: "horse", + 20: "sheep", + 21: "cow", + 22: "elephant", + 23: "bear", + 24: "zebra", + 25: "giraffe", + 27: "backpack", + 28: "umbrella", + 31: "handbag", + 32: "tie", + 33: "suitcase", + 34: "frisbee", + 35: "skis", + 36: "snowboard", + 37: "sports_ball", + 38: "kite", + 39: "baseball_bat", + 40: "baseball_glove", + 41: "skateboard", + 42: "surfboard", + 43: "tennis_racket", + 44: "bottle", + 46: "wine_glass", + 47: "cup", + 48: "fork", + 49: "knife", + 50: "spoon", + 51: "bowl", + 52: "banana", + 53: "apple", + 54: "sandwich", + 55: "orange", + 56: "broccoli", + 57: "carrot", + 58: "hot_dog", + 59: "pizza", + 60: "donut", + 61: "cake", + 62: "chair", + 63: "couch", + 64: "potted_plant", + 65: "bed", + 67: "dining_table", + 70: "toilet", + 72: "tv", + 73: "laptop", + 74: "mouse", + 75: "remote", + 76: "keyboard", + 77: "cell_phone", + 78: "microwave", + 79: "oven", + 80: "toaster", + 81: "sink", + 83: "refrigerator", + 84: "book", + 85: "clock", + 86: "vase", + 87: "scissors", + 88: "teddy_bear", + 89: "hair_drier", + 90: "toothbrush" + } + setattr(context.user_data, "labels", labels) + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run faster_rcnn_inception_v2_coco model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"])) + threshold = float(data.get("threshold", 0.5)) + image = Image.open(buf) + + output_layer = context.user_data.model_handler.infer(image) + + results = [] + prediction = output_layer[0][0] + for obj in prediction: + obj_class = context.labels[int(obj[1])] + obj_value = obj[2] + if obj_value >= threshold: + xtl = obj[3] * image.width + ytl = obj[4] * image.height + xbr = obj[5] * image.width + ybr = obj[6] * image.height + + results.append({ + "label": obj_class, + "points": [xtl, ytl, xbr, ybr], + "type": "rectangle", + "attributes": {} + }) + + return context.Response(body=json.dumps(results), + headers={}, + content_type='application/json', + status_code=200) diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/mapping.json b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/mapping.json new file mode 100644 index 000000000000..bfb65a24cf0c --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/mapping.json @@ -0,0 +1,84 @@ +{ + "label_map": { + "0": "person", + "1": "bicycle", + "2": "car", + "3": "motorbike", + "4": "aeroplane", + "5": "bus", + "6": "train", + "7": "truck", + "8": "boat", + "9": "traffic light", + "10": "fire hydrant", + "11": "stop sign", + "12": "parking meter", + "13": "bench", + "14": "bird", + "15": "cat", + "16": "dog", + "17": "horse", + "18": "sheep", + "19": "cow", + "20": "elephant", + "21": "bear", + "22": "zebra", + "23": "giraffe", + "24": "backpack", + "25": "umbrella", + "26": "handbag", + "27": "tie", + "28": "suitcase", + "29": "frisbee", + "30": "skis", + "31": "snowboard", + "32": "sports ball", + "33": "kite", + "34": "baseball bat", + "35": "baseball glove", + "36": "skateboard", + "37": "surfboard", + "38": "tennis racket", + "39": "bottle", + "40": "wine glass", + "41": "cup", + "42": "fork", + "43": "knife", + "44": "spoon", + "45": "bowl", + "46": "banana", + "47": "apple", + "48": "sandwich", + "49": "orange", + "50": "broccoli", + "51": "carrot", + "52": "hot dog", + "53": "pizza", + "54": "donut", + "55": "cake", + "56": "chair", + "57": "sofa", + "58": "pottedplant", + "59": "bed", + "60": "diningtable", + "61": "toilet", + "62": "tvmonitor", + "63": "laptop", + "64": "mouse", + "65": "remote", + "66": "keyboard", + "67": "cell phone", + "68": "microwave", + "69": "oven", + "70": "toaster", + "71": "sink", + "72": "refrigerator", + "73": "book", + "74": "clock", + "75": "vase", + "76": "scissors", + "77": "teddy bear", + "78": "hair drier", + "79": "toothbrush" + } +} diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/model_loader.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/model_loader.py new file mode 100644 index 000000000000..108febbaa547 --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/model_loader.py @@ -0,0 +1,69 @@ + +# Copyright (C) 2018-2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import cv2 +import numpy as np + +from .inference_engine import make_plugin_or_core, make_network + +class ModelLoader: + def __init__(self, model, weights): + self._model = model + self._weights = weights + + core_or_plugin = make_plugin_or_core() + network = make_network(self._model, self._weights) + + if getattr(core_or_plugin, 'get_supported_layers', False): + supported_layers = core_or_plugin.get_supported_layers(network) + not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] + if len(not_supported_layers) != 0: + raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}". + format(core_or_plugin.device, ", ".join(not_supported_layers))) + + iter_inputs = iter(network.inputs) + self._input_blob_name = next(iter_inputs) + self._input_info_name = '' + self._output_blob_name = next(iter(network.outputs)) + + self._require_image_info = False + + info_names = ('image_info', 'im_info') + + # NOTE: handeling for the inclusion of `image_info` in OpenVino2019 + if any(s in network.inputs for s in info_names): + self._require_image_info = True + self._input_info_name = set(network.inputs).intersection(info_names) + self._input_info_name = self._input_info_name.pop() + if self._input_blob_name in info_names: + self._input_blob_name = next(iter_inputs) + + if getattr(core_or_plugin, 'load_network', False): + self._net = core_or_plugin.load_network(network, + "CPU", + num_requests=2) + else: + self._net = core_or_plugin.load(network=network, num_requests=2) + input_type = network.inputs[self._input_blob_name] + self._input_layout = input_type if isinstance(input_type, list) else input_type.shape + + def infer(self, image): + _, _, h, w = self._input_layout + in_frame = image if image.shape[:-1] == (h, w) else cv2.resize(image, (w, h)) + in_frame = in_frame.transpose((2, 0, 1)) # Change data layout from HWC to CHW + inputs = {self._input_blob_name: in_frame} + if self._require_image_info: + info = np.zeros([1, 3]) + info[0, 0] = h + info[0, 1] = w + # frame number + info[0, 2] = 1 + inputs[self._input_info_name] = info + + results = self._net.infer(inputs) + if len(results) == 1: + return results[self._output_blob_name].copy() + else: + return results.copy() diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/python3 b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/python3 new file mode 100755 index 000000000000..b80f584712d7 --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/python3 @@ -0,0 +1,6 @@ +#!/bin/bash + +args=$@ + +. /opt/intel/openvino/bin/setupvars.sh +/usr/bin/python3 $args diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/requirements.txt b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/requirements.txt new file mode 100644 index 000000000000..5873a2224bf9 --- /dev/null +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/requirements.txt @@ -0,0 +1 @@ +Pillow \ No newline at end of file From af5dda4428eb0b3a23f0502e2eb59c97aa417255 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 9 May 2020 16:11:09 +0300 Subject: [PATCH 11/98] Fix faster_rcnn_inception_v2_coco --- .../nuclio/function.yaml | 4 ++-- .../faster_rcnn_inception_v2_coco/nuclio/main.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml index f2ab2c76c2e9..81d3ff494858 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -23,12 +23,12 @@ spec: value: /opt/nuclio - kind: RUN value: ln -s /usr/bin/pip3 /usr/bin/pip - - postCopy: - kind: RUN value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name faster_rcnn_inception_v2_coco -o /opt/nuclio/open_model_zoo - kind: RUN value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name faster_rcnn_inception_v2_coco --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo + + postCopy: - kind: USER value: openvino diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py index b00acc9f2101..feccc30dfa9e 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py @@ -3,8 +3,7 @@ from PIL import Image import io from model_loader import ModelLoader -import sys -import time +import numpy as np def init_context(context): context.logger.info("Init context... 0%") @@ -104,13 +103,14 @@ def handler(context, event): threshold = float(data.get("threshold", 0.5)) image = Image.open(buf) - output_layer = context.user_data.model_handler.infer(image) + output_layer = context.user_data.model_handler.infer(np.array(image)) results = [] prediction = output_layer[0][0] for obj in prediction: - obj_class = context.labels[int(obj[1])] + obj_class = int(obj[1]) obj_value = obj[2] + obj_label = context.user_data.labels.get(obj_class, "unknown") if obj_value >= threshold: xtl = obj[3] * image.width ytl = obj[4] * image.height @@ -118,10 +118,10 @@ def handler(context, event): ybr = obj[6] * image.height results.append({ - "label": obj_class, + "confidence": str(obj_value), + "label": obj_label, "points": [xtl, ytl, xbr, ybr], "type": "rectangle", - "attributes": {} }) return context.Response(body=json.dumps(results), headers={}, From 6d061fcd2a7c2d0a3f7c833a5288b2872b034019 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sun, 10 May 2020 17:32:17 +0300 Subject: [PATCH 12/98] Implemented mask_rcnn_inception_resnet_v2_atrous_coco --- .../nuclio/README.md | 32 --------- .../nuclio/function.yaml | 16 +++-- .../nuclio/interp.py | 64 ----------------- .../nuclio/main.py | 71 ++++++++++++++++--- .../nuclio/model_loader.py | 2 +- .../nuclio/requirements.txt | 1 - 6 files changed, 72 insertions(+), 114 deletions(-) delete mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/README.md delete mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/interp.py delete mode 100644 serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/requirements.txt diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/README.md b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/README.md deleted file mode 100644 index be7a82121bf0..000000000000 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# mask_rcnn_inception_resnet_v2_atrous_coco - -## Use Case and High-Level Description - -Mask R-CNN Inception Resnet V2 Atrous is trained on COCO dataset and used for object instance segmentation. -For details, see a [paper](https://arxiv.org/pdf/1703.06870.pdf). - -## Specification - -| Metric | Value | -|---------------------------------|-------------------------------------------| -| Type | Instance segmentation | -| GFlops | 675.314 | -| MParams | 92.368 | -| Source framework | TensorFlow\* | - -## Legal Information - -[https://raw.githubusercontent.com/tensorflow/models/master/LICENSE]() - -## OpenVINO Conversion Notes - -In order to convert the code into the openvino format, please see the [following link](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_Object_Detection_API_Models.html#mask_r_cnn_topologies). - -The conversion command from the command line prompt will look something like the following. - -```shell -$ python /opt/intel/openvino/deployment_tools/model_optimizer/mo_tf.py \ - --input_model /path/to/frozen_inference_graph.pb \ - --tensorflow_use_custom_operations_config /opt/intel/openvino/deployment_tools/model_optimizer/extensions/front/tf/mask_rcnn_support.json \ - --tensorflow_object_detection_api_pipeline_config /path/to/pipeline.config -``` diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml index 8b17196d6ba3..0febfca25fcf 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -1,19 +1,19 @@ metadata: - name: open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco + name: omz.public.mask_rcnn_inception_resnet_v2_atrous_coco namespace: cvat spec: - description: Mask RCNN inception resnet v2 from Intel Open Model Zoo + description: Mask RCNN inception resnet v2 COCO from Intel Open Model Zoo runtime: "python:3.6" handler: main:handler - eventTimeout: 30s + eventTimeout: 60s env: - name: NUCLIO_PYTHON_EXE_PATH value: /opt/nuclio/python3 build: image: cvat/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco - baseImage: openvino/ubuntu18_runtime:2020.2 + baseImage: openvino/ubuntu18_dev:2020.2 directives: preCopy: @@ -23,10 +23,16 @@ spec: value: /opt/nuclio - kind: RUN value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name mask_rcnn_inception_resnet_v2_atrous_coco -o /opt/nuclio/open_model_zoo + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name mask_rcnn_inception_resnet_v2_atrous_coco --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo postCopy: - kind: RUN - value: pip3 install -r requirements.txt + value: apt update && DEBIAN_FRONTEND=noninteractive apt install --no-install-recommends -y python3-skimage + - kind: RUN + value: pip3 install "numpy<1.16.0" # workaround for skimage - kind: USER value: openvino diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/interp.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/interp.py deleted file mode 100644 index 6625a8349330..000000000000 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/interp.py +++ /dev/null @@ -1,64 +0,0 @@ -import numpy as np -import cv2 -from skimage.measure import approximate_polygon, find_contours - - -MASK_THRESHOLD = .5 -PROBABILITY_THRESHOLD = 0.2 - - -# Ref: https://software.intel.com/en-us/forums/computer-vision/topic/804895 -def segm_postprocess(box: list, raw_cls_mask, im_h, im_w, threshold): - ymin, xmin, ymax, xmax = box - - width = int(abs(xmax - xmin)) - height = int(abs(ymax - ymin)) - - result = np.zeros((im_h, im_w), dtype=np.uint8) - resized_mask = cv2.resize(raw_cls_mask, dsize=(height, width), interpolation=cv2.INTER_CUBIC) - - # extract the ROI of the image - ymin = int(round(ymin)) - xmin = int(round(xmin)) - ymax = ymin + height - xmax = xmin + width - result[xmin:xmax, ymin:ymax] = (resized_mask>threshold).astype(np.uint8) * 255 - - return result - - -for detection in detections: - frame_number = detection['frame_id'] - height = detection['frame_height'] - width = detection['frame_width'] - detection = detection['detections'] - - masks = detection['masks'] - boxes = detection['reshape_do_2d'] - - for index, box in enumerate(boxes): - label = int(box[1]) - obj_value = box[2] - if obj_value >= PROBABILITY_THRESHOLD: - x = box[3] * width - y = box[4] * height - right = box[5] * width - bottom = box[6] * height - mask = masks[index][label - 1] - - mask = segm_postprocess((x, y, right, bottom), - mask, - height, - width, - MASK_THRESHOLD) - - contours = find_contours(mask, MASK_THRESHOLD) - contour = contours[0] - contour = np.flip(contour, axis=1) - contour = approximate_polygon(contour, tolerance=2.5) - segmentation = contour.tolist() - - - # NOTE: if you want to see the boxes, uncomment next line - # results.add_box(x, y, right, bottom, label, frame_number) - results.add_polygon(segmentation, label, frame_number) diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py index 140773753f2f..3f1f18e89566 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py @@ -2,14 +2,15 @@ import base64 from PIL import Image import io -from .model_loader import ModelLoader -import sys -import time +from model_loader import ModelLoader +import numpy as np +import cv2 +from skimage.measure import approximate_polygon, find_contours def init_context(context): context.logger.info("Init context... 0%") - model_xml = "./fmask_rcnn_inception_resnet_v2_atrous_coco.xml" - model_bin = "./mask_rcnn_inception_resnet_v2_atrous_coco.bin" + model_xml = "/opt/nuclio/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/FP32/mask_rcnn_inception_resnet_v2_atrous_coco.xml" + model_bin = "/opt/nuclio/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/FP32/mask_rcnn_inception_resnet_v2_atrous_coco.bin" model_handler = ModelLoader(model_xml, model_bin) setattr(context.user_data, 'model_handler', model_handler) labels = { @@ -97,18 +98,66 @@ def init_context(context): setattr(context.user_data, "labels", labels) context.logger.info("Init context...100%") +MASK_THRESHOLD = 0.5 + +# Ref: https://software.intel.com/en-us/forums/computer-vision/topic/804895 +def segm_postprocess(box: list, raw_cls_mask, im_h, im_w): + ymin, xmin, ymax, xmax = box + + width = int(abs(xmax - xmin)) + height = int(abs(ymax - ymin)) + + result = np.zeros((im_h, im_w), dtype=np.uint8) + resized_mask = cv2.resize(raw_cls_mask, dsize=(height, width), interpolation=cv2.INTER_CUBIC) + + # extract the ROI of the image + ymin = int(round(ymin)) + xmin = int(round(xmin)) + ymax = ymin + height + xmax = xmin + width + result[xmin:xmax, ymin:ymax] = (resized_mask>MASK_THRESHOLD).astype(np.uint8) * 255 + + return result + def handler(context, event): context.logger.info("Run mask_rcnn_inception_resnet_v2_atrous_coco model") data = event.body buf = io.BytesIO(base64.b64decode(data["image"])) - threshold = float(data.get("threshold", 0.5)) + threshold = float(data.get("threshold", 0.2)) image = Image.open(buf) - output_layer = context.user_data.model_handler.infer(image) + output_layer = context.user_data.model_handler.infer(np.array(image)) results = [] + masks = output_layer['masks'] + boxes = output_layer['reshape_do_2d'] + + for index, box in enumerate(boxes): + obj_class = int(box[1]) + obj_value = box[2] + obj_label = context.user_data.labels.get(obj_class, "unknown") + if obj_value >= threshold: + xtl = box[3] * image.width + ytl = box[4] * image.height + xbr = box[5] * image.width + ybr = box[6] * image.height + mask = masks[index][obj_class - 1] + + mask = segm_postprocess((xtl, ytl, xbr, ybr), + mask, image.height, image.width) + + contours = find_contours(mask, MASK_THRESHOLD) + contour = contours[0] + contour = np.flip(contour, axis=1) + contour = approximate_polygon(contour, tolerance=2.5) + polygon = contour.tolist() + + results.append({ + "confidence": str(obj_value), + "label": obj_label, + "points": polygon, + "type": "polygon", + }) - return context.Response(body=json.dumps(results), - headers={}, - content_type='application/json', - status_code=200) + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_loader.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_loader.py index 108febbaa547..c70baf6ea03b 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_loader.py +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/model_loader.py @@ -6,7 +6,7 @@ import cv2 import numpy as np -from .inference_engine import make_plugin_or_core, make_network +from inference_engine import make_plugin_or_core, make_network class ModelLoader: def __init__(self, model, weights): diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/requirements.txt b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/requirements.txt deleted file mode 100644 index 5873a2224bf9..000000000000 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Pillow \ No newline at end of file From 8dbfeb9a410c81d8629e02852e0f9eebe6acd193 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 11 May 2020 23:33:19 +0300 Subject: [PATCH 13/98] Implemented yolo detector as a lambda function --- .../public/yolov-v3-tf/nuclio/README.md | 22 -- .../public/yolov-v3-tf/nuclio/function.yaml | 14 +- .../public/yolov-v3-tf/nuclio/interp.py | 160 --------- .../public/yolov-v3-tf/nuclio/main.py | 333 ++++++++++++------ .../public/yolov-v3-tf/nuclio/mapping.json | 84 ----- .../public/yolov-v3-tf/nuclio/model_loader.py | 10 +- .../yolov-v3-tf/nuclio/requirements.txt | 1 - 7 files changed, 247 insertions(+), 377 deletions(-) delete mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/README.md delete mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/interp.py delete mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/mapping.json delete mode 100644 serverless/open_model_zoo/public/yolov-v3-tf/nuclio/requirements.txt diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/README.md b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/README.md deleted file mode 100644 index 2e47953cb3f8..000000000000 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Object Detection YOLO V3 Python Demo, Async API Performance Showcase - -See [these instructions][1] for converting the yolo weights to the OpenVino format. - -As of OpenVINO 2019 R3, only tensorflow 1.13 and NetworkX 2.3. -These can be explicitly installed using the following command. - -```bash -python3 -m pip install tensorflow==1.13 networkx==2.3 -``` - - -Additionally, at the time of writing, the model optimizer required an input shape. - -``` bash -python3 mo_tf.py \ - --input_model /path/to/yolo_v3.pb \ - --tensorflow_use_custom_operations_config $MO_ROOT/extensions/front/tf/yolo_v3.json \ - --input_shape [1,416,416,3] -``` - -[1]: https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_YOLO_From_Tensorflow.html diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml index 0b012863439c..52e383285911 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml @@ -1,9 +1,9 @@ metadata: - name: open_model_zoo/public/faster_rcnn_inception_v2_coco + name: omz.public.yolo-v3-tf namespace: cvat spec: - description: Faster RCNN inception v2 COCO from Intel Open Model Zoo + description: YOLO v3 from Intel Open Model Zoo runtime: "python:3.6" handler: main:handler eventTimeout: 30s @@ -12,8 +12,8 @@ spec: value: /opt/nuclio/python3 build: - image: cvat/open_model_zoo/public/faster_rcnn_inception_v2_coco - baseImage: openvino/ubuntu18_runtime:2020.2 + image: cvat/open_model_zoo/public/yolo-v3-tf + baseImage: openvino/ubuntu18_dev:2020.2 directives: preCopy: @@ -23,10 +23,12 @@ spec: value: /opt/nuclio - kind: RUN value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name yolo-v3-tf -o /opt/nuclio/open_model_zoo + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name yolo-v3-tf --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo postCopy: - - kind: RUN - value: pip3 install -r requirements.txt - kind: USER value: openvino diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/interp.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/interp.py deleted file mode 100644 index 4c76c85448d0..000000000000 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/interp.py +++ /dev/null @@ -1,160 +0,0 @@ -from math import exp - - -class Parser: - IOU_THRESHOLD = 0.4 - PROB_THRESHOLD = 0.5 - - def __init__(self): - self.objects = [] - - def scale_bbox(self, x, y, h, w, class_id, confidence, h_scale, w_scale): - xmin = int((x - w / 2) * w_scale) - ymin = int((y - h / 2) * h_scale) - xmax = int(xmin + w * w_scale) - ymax = int(ymin + h * h_scale) - - return dict(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, class_id=class_id, confidence=confidence) - - def entry_index(self, side, coord, classes, location, entry): - side_power_2 = side ** 2 - n = location // side_power_2 - loc = location % side_power_2 - return int(side_power_2 * (n * (coord + classes + 1) + entry) + loc) - - def intersection_over_union(self, box_1, box_2): - width_of_overlap_area = min(box_1['xmax'], box_2['xmax']) - max(box_1['xmin'], box_2['xmin']) - height_of_overlap_area = min(box_1['ymax'], box_2['ymax']) - max(box_1['ymin'], box_2['ymin']) - if width_of_overlap_area < 0 or height_of_overlap_area < 0: - area_of_overlap = 0 - else: - area_of_overlap = width_of_overlap_area * height_of_overlap_area - box_1_area = (box_1['ymax'] - box_1['ymin']) * (box_1['xmax'] - box_1['xmin']) - box_2_area = (box_2['ymax'] - box_2['ymin']) * (box_2['xmax'] - box_2['xmin']) - area_of_union = box_1_area + box_2_area - area_of_overlap - if area_of_union == 0: - return 0 - return area_of_overlap / area_of_union - - - def sort_objects(self): - self.objects = sorted(self.objects, key=lambda obj : obj['confidence'], reverse=True) - - for i in range(len(self.objects)): - if self.objects[i]['confidence'] == 0: - continue - for j in range(i + 1, len(self.objects)): - if self.intersection_over_union(self.objects[i], self.objects[j]) > self.IOU_THRESHOLD: - self.objects[j]['confidence'] = 0 - - def parse_yolo_region(self, blob: 'np.ndarray', original_shape: list, params: dict) -> list: - - # YOLO magic numbers - # See: https://github.com/opencv/open_model_zoo/blob/acf297c73db8cb3f68791ae1fad4a7cc4a6039e5/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L61 - num = 3 - coords = 4 - classes = 80 - # ----------------- - - _, _, out_blob_h, out_blob_w = blob.shape - assert out_blob_w == out_blob_h, "Invalid size of output blob. It sould be in NCHW layout and height should " \ - "be equal to width. Current height = {}, current width = {}" \ - "".format(out_blob_h, out_blob_w) - - # ------ Extracting layer parameters -- - orig_im_h, orig_im_w = original_shape - predictions = blob.flatten() - side_square = params['side'] * params['side'] - - # ------ Parsing YOLO Region output -- - for i in range(side_square): - row = i // params['side'] - col = i % params['side'] - for n in range(num): - # -----entry index calcs------ - obj_index = self.entry_index(params['side'], coords, classes, n * side_square + i, coords) - scale = predictions[obj_index] - if scale < self.PROB_THRESHOLD: - continue - box_index = self.entry_index(params['side'], coords, classes, n * side_square + i, 0) - - # Network produces location predictions in absolute coordinates of feature maps. - # Scale it to relative coordinates. - x = (col + predictions[box_index + 0 * side_square]) / params['side'] * 416 - y = (row + predictions[box_index + 1 * side_square]) / params['side'] * 416 - # Value for exp is very big number in some cases so following construction is using here - try: - h_exp = exp(predictions[box_index + 3 * side_square]) - w_exp = exp(predictions[box_index + 2 * side_square]) - except OverflowError: - continue - - w = w_exp * params['anchors'][2 * n] - h = h_exp * params['anchors'][2 * n + 1] - - for j in range(classes): - class_index = self.entry_index(params['side'], coords, classes, n * side_square + i, - coords + 1 + j) - confidence = scale * predictions[class_index] - if confidence < self.PROB_THRESHOLD: - continue - - self.objects.append(self.scale_bbox(x=x, - y=y, - h=h, - w=w, - class_id=j, - confidence=confidence, - h_scale=(orig_im_h/416), - w_scale=(orig_im_w/416))) - - -for detection in detections: - frame_number = detection['frame_id'] - height = detection['frame_height'] - width = detection['frame_width'] - detection = detection['detections'] - - original_shape = (height, width) - - # https://github.com/opencv/open_model_zoo/blob/master/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L72 - anchors = [10,13,16,30,33,23,30,61,62,45,59,119,116,90,156,198,373,326] - conv_6 = {'side': 13, 'mask': [6,7,8]} - conv_14 = {'side': 26, 'mask': [3,4,5]} - conv_22 = {'side': 52, 'mask': [0,1,2]} - - yolo_params = {'detector/yolo-v3/Conv_6/BiasAdd/YoloRegion': conv_6, - 'detector/yolo-v3/Conv_14/BiasAdd/YoloRegion': conv_14, - 'detector/yolo-v3/Conv_22/BiasAdd/YoloRegion': conv_22} - - for conv_net in yolo_params.values(): - mask = conv_net['mask'] - masked_anchors = [] - for idx in mask: - masked_anchors += [anchors[idx * 2], anchors[idx * 2 + 1]] - - conv_net['anchors'] = masked_anchors - - parser = Parser() - - for name, blob in detection.items(): - parser.parse_yolo_region(blob, original_shape, yolo_params[name]) - - parser.sort_objects() - - objects = [] - for obj in parser.objects: - if obj['confidence'] >= parser.PROB_THRESHOLD: - label = obj['class_id'] - xmin = obj['xmin'] - xmax = obj['xmax'] - ymin = obj['ymin'] - ymax = obj['ymax'] - - # Enforcing extra checks for bounding box coordinates - xmin = max(0,xmin) - ymin = max(0,ymin) - xmax = min(xmax,width) - ymax = min(ymax,height) - - results.add_box(xmin, ymin, xmax, ymax, label, frame_number) diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py index 5b4c754a0598..c519671b4921 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py @@ -2,129 +2,256 @@ import base64 from PIL import Image import io -from .model_loader import ModelLoader -import sys -import time +from model_loader import ModelLoader +import numpy as np +from math import exp + def init_context(context): context.logger.info("Init context... 0%") - model_xml = "./faster_rcnn_inception_v2_coco.xml" - model_bin = "./faster_rcnn_inception_v2_coco.bin" + model_xml = "/opt/nuclio/open_model_zoo/public/yolo-v3-tf/FP32/yolo-v3-tf.xml" + model_bin = "/opt/nuclio/open_model_zoo/public/yolo-v3-tf/FP32/yolo-v3-tf.bin" model_handler = ModelLoader(model_xml, model_bin) setattr(context.user_data, 'model_handler', model_handler) labels = { - 1: "person", - 2: "bicycle", - 3: "car", - 4: "motorcycle", - 5: "airplane", - 6: "bus", - 7: "train", - 8: "truck", - 9: "boat", - 10: "traffic_light", - 11: "fire_hydrant", - 13: "stop_sign", - 14: "parking_meter", - 15: "bench", - 16: "bird", - 17: "cat", - 18: "dog", - 19: "horse", - 20: "sheep", - 21: "cow", - 22: "elephant", - 23: "bear", - 24: "zebra", - 25: "giraffe", - 27: "backpack", - 28: "umbrella", - 31: "handbag", - 32: "tie", - 33: "suitcase", - 34: "frisbee", - 35: "skis", - 36: "snowboard", - 37: "sports_ball", - 38: "kite", - 39: "baseball_bat", - 40: "baseball_glove", - 41: "skateboard", - 42: "surfboard", - 43: "tennis_racket", - 44: "bottle", - 46: "wine_glass", - 47: "cup", - 48: "fork", - 49: "knife", - 50: "spoon", - 51: "bowl", - 52: "banana", - 53: "apple", - 54: "sandwich", - 55: "orange", - 56: "broccoli", - 57: "carrot", - 58: "hot_dog", - 59: "pizza", - 60: "donut", - 61: "cake", - 62: "chair", - 63: "couch", - 64: "potted_plant", - 65: "bed", - 67: "dining_table", - 70: "toilet", - 72: "tv", - 73: "laptop", - 74: "mouse", - 75: "remote", - 76: "keyboard", - 77: "cell_phone", - 78: "microwave", - 79: "oven", - 80: "toaster", - 81: "sink", - 83: "refrigerator", - 84: "book", - 85: "clock", - 86: "vase", - 87: "scissors", - 88: "teddy_bear", - 89: "hair_drier", - 90: "toothbrush" + 0: "person", + 1: "bicycle", + 2: "car", + 3: "motorbike", + 4: "aeroplane", + 5: "bus", + 6: "train", + 7: "truck", + 8: "boat", + 9: "traffic light", + 10: "fire hydrant", + 11: "stop sign", + 12: "parking meter", + 13: "bench", + 14: "bird", + 15: "cat", + 16: "dog", + 17: "horse", + 18: "sheep", + 19: "cow", + 20: "elephant", + 21: "bear", + 22: "zebra", + 23: "giraffe", + 24: "backpack", + 25: "umbrella", + 26: "handbag", + 27: "tie", + 28: "suitcase", + 29: "frisbee", + 30: "skis", + 31: "snowboard", + 32: "sports ball", + 33: "kite", + 34: "baseball bat", + 35: "baseball glove", + 36: "skateboard", + 37: "surfboard", + 38: "tennis racket", + 39: "bottle", + 40: "wine glass", + 41: "cup", + 42: "fork", + 43: "knife", + 44: "spoon", + 45: "bowl", + 46: "banana", + 47: "apple", + 48: "sandwich", + 49: "orange", + 50: "broccoli", + 51: "carrot", + 52: "hot dog", + 53: "pizza", + 54: "donut", + 55: "cake", + 56: "chair", + 57: "sofa", + 58: "pottedplant", + 59: "bed", + 60: "diningtable", + 61: "toilet", + 62: "tvmonitor", + 63: "laptop", + 64: "mouse", + 65: "remote", + 66: "keyboard", + 67: "cell phone", + 68: "microwave", + 69: "oven", + 70: "toaster", + 71: "sink", + 72: "refrigerator", + 73: "book", + 74: "clock", + 75: "vase", + 76: "scissors", + 77: "teddy bear", + 78: "hair drier", + 79: "toothbrush" } setattr(context.user_data, "labels", labels) context.logger.info("Init context...100%") +class YoloParams: + # ------------------------------------------- Extracting layer parameters ------------------------------------------ + # Magic numbers are copied from yolo samples + def __init__(self, param, side): + self.num = 3 if 'num' not in param else int(param['num']) + self.coords = 4 if 'coords' not in param else int(param['coords']) + self.classes = 80 if 'classes' not in param else int(param['classes']) + self.side = side + self.anchors = [10.0, 13.0, 16.0, 30.0, 33.0, 23.0, 30.0, 61.0, 62.0, 45.0, 59.0, 119.0, 116.0, 90.0, 156.0, + 198.0, + 373.0, 326.0] if 'anchors' not in param else [float(a) for a in param['anchors'].split(',')] + + self.isYoloV3 = False + + if param.get('mask'): + mask = [int(idx) for idx in param['mask'].split(',')] + self.num = len(mask) + + maskedAnchors = [] + for idx in mask: + maskedAnchors += [self.anchors[idx * 2], self.anchors[idx * 2 + 1]] + self.anchors = maskedAnchors + + self.isYoloV3 = True # Weak way to determine but the only one. + + def log_params(self): + params_to_print = {'classes': self.classes, 'num': self.num, 'coords': self.coords, 'anchors': self.anchors} + +def entry_index(side, coord, classes, location, entry): + side_power_2 = side ** 2 + n = location // side_power_2 + loc = location % side_power_2 + return int(side_power_2 * (n * (coord + classes + 1) + entry) + loc) + + +def scale_bbox(x, y, h, w, class_id, confidence, h_scale, w_scale): + xmin = int((x - w / 2) * w_scale) + ymin = int((y - h / 2) * h_scale) + xmax = int(xmin + w * w_scale) + ymax = int(ymin + h * h_scale) + return dict(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, class_id=class_id, confidence=confidence) + + +def parse_yolo_region(blob, resized_image_shape, original_im_shape, params, threshold): + # ------------------------------------------ Validating output parameters ------------------------------------------ + _, _, out_blob_h, out_blob_w = blob.shape + assert out_blob_w == out_blob_h, "Invalid size of output blob. It sould be in NCHW layout and height should " \ + "be equal to width. Current height = {}, current width = {}" \ + "".format(out_blob_h, out_blob_w) + + # ------------------------------------------ Extracting layer parameters ------------------------------------------- + orig_im_h, orig_im_w = original_im_shape + resized_image_h, resized_image_w = resized_image_shape + objects = list() + predictions = blob.flatten() + side_square = params.side * params.side + + # ------------------------------------------- Parsing YOLO Region output ------------------------------------------- + for i in range(side_square): + row = i // params.side + col = i % params.side + for n in range(params.num): + obj_index = entry_index(params.side, params.coords, params.classes, n * side_square + i, params.coords) + scale = predictions[obj_index] + if scale < threshold: + continue + box_index = entry_index(params.side, params.coords, params.classes, n * side_square + i, 0) + # Network produces location predictions in absolute coordinates of feature maps. + # Scale it to relative coordinates. + x = (col + predictions[box_index + 0 * side_square]) / params.side + y = (row + predictions[box_index + 1 * side_square]) / params.side + # Value for exp is very big number in some cases so following construction is using here + try: + w_exp = exp(predictions[box_index + 2 * side_square]) + h_exp = exp(predictions[box_index + 3 * side_square]) + except OverflowError: + continue + # Depends on topology we need to normalize sizes by feature maps (up to YOLOv3) or by input shape (YOLOv3) + w = w_exp * params.anchors[2 * n] / (resized_image_w if params.isYoloV3 else params.side) + h = h_exp * params.anchors[2 * n + 1] / (resized_image_h if params.isYoloV3 else params.side) + for j in range(params.classes): + class_index = entry_index(params.side, params.coords, params.classes, n * side_square + i, + params.coords + 1 + j) + confidence = scale * predictions[class_index] + if confidence < threshold: + continue + objects.append(scale_bbox(x=x, y=y, h=h, w=w, class_id=j, confidence=confidence, + h_scale=orig_im_h, w_scale=orig_im_w)) + return objects + + +def intersection_over_union(box_1, box_2): + width_of_overlap_area = min(box_1['xmax'], box_2['xmax']) - max(box_1['xmin'], box_2['xmin']) + height_of_overlap_area = min(box_1['ymax'], box_2['ymax']) - max(box_1['ymin'], box_2['ymin']) + if width_of_overlap_area < 0 or height_of_overlap_area < 0: + area_of_overlap = 0 + else: + area_of_overlap = width_of_overlap_area * height_of_overlap_area + box_1_area = (box_1['ymax'] - box_1['ymin']) * (box_1['xmax'] - box_1['xmin']) + box_2_area = (box_2['ymax'] - box_2['ymin']) * (box_2['xmax'] - box_2['xmin']) + area_of_union = box_1_area + box_2_area - area_of_overlap + if area_of_union == 0: + return 0 + return area_of_overlap / area_of_union + + def handler(context, event): - context.logger.info("Run faster_rcnn_inception_v2_coco model") + context.logger.info("Run yolo-v3-tf model") data = event.body buf = io.BytesIO(base64.b64decode(data["image"])) - threshold = float(data.get("threshold", 0.5)) + threshold = float(data.get("threshold", 0.2)) image = Image.open(buf) - output_layer = context.user_data.model_handler.infer(image) + output_layer = context.user_data.model_handler.infer(np.array(image)) results = [] - prediction = output_layer[0][0] - for obj in prediction: - obj_class = context.labels[int(obj[1])] - obj_value = obj[2] - if obj_value >= threshold: - xtl = obj[3] * image.width - ytl = obj[4] * image.height - xbr = obj[5] * image.width - ybr = obj[6] * image.height + # Collecting object detection results + objects = [] + PROB_THRESHOLD = 0.5 + model = context.user_data.model_handler + origin_im_size = (image.height, image.width) + for layer_name, out_blob in output_layer.items(): + out_blob = out_blob.reshape(model.layers[model.layers[layer_name].parents[0]].shape) + layer_params = YoloParams(model.layers[layer_name].params, out_blob.shape[2]) + objects += parse_yolo_region(out_blob, model.input_size(), + origin_im_size, layer_params, + PROB_THRESHOLD) + + # Filtering overlapping boxes with respect to the --iou_threshold CLI parameter + IOU_THRESHOLD = 0.4 + objects = sorted(objects, key=lambda obj : obj['confidence'], reverse=True) + for i in range(len(objects)): + if objects[i]['confidence'] == 0: + continue + for j in range(i + 1, len(objects)): + if intersection_over_union(objects[i], objects[j]) > IOU_THRESHOLD: + objects[j]['confidence'] = 0 + + objects = [obj for obj in objects if obj['confidence'] >= PROB_THRESHOLD] + + for obj in objects: + if obj['confidence'] >= PROB_THRESHOLD: + xtl = max(obj['xmin'], 0) + ytl = max(obj['ymin'], 0) + xbr = min(obj['xmax'], image.width) + ybr = min(obj['ymax'], image.height) + obj_class = int(obj['class_id']) results.append({ - "label": obj_class, + "confidence": str(obj['confidence']), + "label": context.user_data.labels.get(obj_class, "unknown"), "points": [xtl, ytl, xbr, ybr], "type": "rectangle", - "attributes": {} }) - return context.Response(body=json.dumps(results), - headers={}, - content_type='application/json', - status_code=200) + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/mapping.json b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/mapping.json deleted file mode 100644 index bfb65a24cf0c..000000000000 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/mapping.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "label_map": { - "0": "person", - "1": "bicycle", - "2": "car", - "3": "motorbike", - "4": "aeroplane", - "5": "bus", - "6": "train", - "7": "truck", - "8": "boat", - "9": "traffic light", - "10": "fire hydrant", - "11": "stop sign", - "12": "parking meter", - "13": "bench", - "14": "bird", - "15": "cat", - "16": "dog", - "17": "horse", - "18": "sheep", - "19": "cow", - "20": "elephant", - "21": "bear", - "22": "zebra", - "23": "giraffe", - "24": "backpack", - "25": "umbrella", - "26": "handbag", - "27": "tie", - "28": "suitcase", - "29": "frisbee", - "30": "skis", - "31": "snowboard", - "32": "sports ball", - "33": "kite", - "34": "baseball bat", - "35": "baseball glove", - "36": "skateboard", - "37": "surfboard", - "38": "tennis racket", - "39": "bottle", - "40": "wine glass", - "41": "cup", - "42": "fork", - "43": "knife", - "44": "spoon", - "45": "bowl", - "46": "banana", - "47": "apple", - "48": "sandwich", - "49": "orange", - "50": "broccoli", - "51": "carrot", - "52": "hot dog", - "53": "pizza", - "54": "donut", - "55": "cake", - "56": "chair", - "57": "sofa", - "58": "pottedplant", - "59": "bed", - "60": "diningtable", - "61": "toilet", - "62": "tvmonitor", - "63": "laptop", - "64": "mouse", - "65": "remote", - "66": "keyboard", - "67": "cell phone", - "68": "microwave", - "69": "oven", - "70": "toaster", - "71": "sink", - "72": "refrigerator", - "73": "book", - "74": "clock", - "75": "vase", - "76": "scissors", - "77": "teddy bear", - "78": "hair drier", - "79": "toothbrush" - } -} diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/model_loader.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/model_loader.py index 108febbaa547..8e94c0ddcd73 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/model_loader.py +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/model_loader.py @@ -6,7 +6,7 @@ import cv2 import numpy as np -from .inference_engine import make_plugin_or_core, make_network +from inference_engine import make_plugin_or_core, make_network class ModelLoader: def __init__(self, model, weights): @@ -48,6 +48,7 @@ def __init__(self, model, weights): self._net = core_or_plugin.load(network=network, num_requests=2) input_type = network.inputs[self._input_blob_name] self._input_layout = input_type if isinstance(input_type, list) else input_type.shape + self._network = network def infer(self, image): _, _, h, w = self._input_layout @@ -67,3 +68,10 @@ def infer(self, image): return results[self._output_blob_name].copy() else: return results.copy() + + def input_size(self): + return self._input_layout[2:] + + @property + def layers(self): + return self._network.layers diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/requirements.txt b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/requirements.txt deleted file mode 100644 index 5873a2224bf9..000000000000 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Pillow \ No newline at end of file From bb92b4a7647a8538ec59f74a609452ca39fd20e7 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 18 May 2020 10:57:19 +0300 Subject: [PATCH 14/98] Removed dextr app. --- CONTRIBUTING.md | 13 -- Dockerfile | 10 - cvat/apps/dextr_segmentation/README.md | 31 --- cvat/apps/dextr_segmentation/__init__.py | 7 - cvat/apps/dextr_segmentation/apps.py | 8 - cvat/apps/dextr_segmentation/dextr.py | 119 ---------- .../docker-compose.dextr.yml | 14 -- .../dextr_segmentation/js/enginePlugin.js | 214 ------------------ cvat/apps/dextr_segmentation/urls.py | 13 -- cvat/apps/dextr_segmentation/views.py | 128 ----------- cvat/apps/documentation/installation.md | 48 ++-- cvat/apps/lambda_manager/urls.py | 4 +- cvat/settings/base.py | 3 - cvat/urls.py | 3 - cvat_proxy/conf.d/cvat.conf.template | 2 +- 15 files changed, 29 insertions(+), 588 deletions(-) delete mode 100644 cvat/apps/dextr_segmentation/README.md delete mode 100644 cvat/apps/dextr_segmentation/__init__.py delete mode 100644 cvat/apps/dextr_segmentation/apps.py delete mode 100644 cvat/apps/dextr_segmentation/dextr.py delete mode 100644 cvat/apps/dextr_segmentation/docker-compose.dextr.yml delete mode 100644 cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js delete mode 100644 cvat/apps/dextr_segmentation/urls.py delete mode 100644 cvat/apps/dextr_segmentation/views.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b02c62cc651..8017d9840679 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,19 +115,6 @@ to changes in ``.env/bin/activate`` file are active. export REID_MODEL_DIR="/path/to/dir" # dir must contain .xml and .bin files ``` -### Deep Extreme Cut -- Perform all steps in the automatic annotation section -- Download Deep Extreme Cut model, unpack it, and save somewhere: -```sh -curl https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -o dextr.zip -unzip dextr.zip -``` -- Add next lines to ``.env/bin/activate``: -```sh - export WITH_DEXTR="yes" - export DEXTR_MODEL_DIR="/path/to/dir" # dir must contain .xml and .bin files -``` - ### Tensorflow RCNN - Download RCNN model, unpack it, and save it somewhere: ```sh diff --git a/Dockerfile b/Dockerfile index 9c00bf8f0494..74d67e2698e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -119,16 +119,6 @@ RUN if [ "$CUDA_SUPPORT" = "yes" ]; then \ /tmp/components/cuda/install.sh; \ fi -# TODO: CHANGE URL -ARG WITH_DEXTR -ENV WITH_DEXTR=${WITH_DEXTR} -ENV DEXTR_MODEL_DIR=${HOME}/dextr -RUN if [ "$WITH_DEXTR" = "yes" ]; then \ - mkdir ${DEXTR_MODEL_DIR} -p && \ - curl https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -o ${DEXTR_MODEL_DIR}/dextr.zip && \ - 7z e ${DEXTR_MODEL_DIR}/dextr.zip -o${DEXTR_MODEL_DIR} && rm ${DEXTR_MODEL_DIR}/dextr.zip; \ - fi - COPY ssh ${HOME}/.ssh COPY utils ${HOME}/utils COPY cvat/ ${HOME}/cvat diff --git a/cvat/apps/dextr_segmentation/README.md b/cvat/apps/dextr_segmentation/README.md deleted file mode 100644 index 2382a380861d..000000000000 --- a/cvat/apps/dextr_segmentation/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Semi-Automatic Segmentation with [Deep Extreme Cut](http://www.vision.ee.ethz.ch/~cvlsegmentation/dextr/) - -## About the application - -The application allows to use deep learning models for semi-automatic semantic and instance segmentation. -You can get a segmentation polygon from four (or more) extreme points of an object. -This application uses the pre-trained DEXTR model which has been converted to Inference Engine format. - -We are grateful to K.K. Maninis, S. Caelles, J. Pont-Tuset, and L. Van Gool who permitted using their models in our tool - -## Build docker image -```bash -# OpenVINO component is also needed -docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml -f cvat/apps/dextr_segmentation/docker-compose.dextr.yml build -``` - -## Run docker container -```bash -docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml -f cvat/apps/dextr_segmentation/docker-compose.dextr.yml up -d -``` - -## Using - -1. Open a job -2. Select "Auto Segmentation" in the list of shapes -3. Run the draw mode as usually (by press the "Create Shape" button or by "N" shortcut) -4. Click four-six (or more if it's need) extreme points of an object -5. Close the draw mode as usually (by shortcut or pressing the button "Stop Creation") -6. Wait a moment and you will get a class agnostic annotation polygon -7. You can close an annotation request if it is too long -(in case if it is queued to rq worker and all workers are busy) diff --git a/cvat/apps/dextr_segmentation/__init__.py b/cvat/apps/dextr_segmentation/__init__.py deleted file mode 100644 index 472c2ac3c42f..000000000000 --- a/cvat/apps/dextr_segmentation/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -JS_3RDPARTY['engine'] = JS_3RDPARTY.get('engine', []) + ['dextr_segmentation/js/enginePlugin.js'] diff --git a/cvat/apps/dextr_segmentation/apps.py b/cvat/apps/dextr_segmentation/apps.py deleted file mode 100644 index d5d43a88e1c0..000000000000 --- a/cvat/apps/dextr_segmentation/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - -class DextrSegmentationConfig(AppConfig): - name = 'dextr_segmentation' diff --git a/cvat/apps/dextr_segmentation/dextr.py b/cvat/apps/dextr_segmentation/dextr.py deleted file mode 100644 index d6eb20022248..000000000000 --- a/cvat/apps/dextr_segmentation/dextr.py +++ /dev/null @@ -1,119 +0,0 @@ - -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network -from cvat.apps.engine.frame_provider import FrameProvider - -import os -import cv2 -import PIL -import numpy as np - -_IE_CPU_EXTENSION = os.getenv("IE_CPU_EXTENSION", "libcpu_extension_avx2.so") -_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) - -_DEXTR_MODEL_DIR = os.getenv("DEXTR_MODEL_DIR", None) -_DEXTR_PADDING = 50 -_DEXTR_TRESHOLD = 0.9 -_DEXTR_SIZE = 512 - -class DEXTR_HANDLER: - def __init__(self): - self._plugin = None - self._network = None - self._exec_network = None - self._input_blob = None - self._output_blob = None - if not _DEXTR_MODEL_DIR: - raise Exception("DEXTR_MODEL_DIR is not defined") - - - def handle(self, db_data, frame, points): - # Lazy initialization - if not self._plugin: - self._plugin = make_plugin_or_core() - self._network = make_network(os.path.join(_DEXTR_MODEL_DIR, 'dextr.xml'), - os.path.join(_DEXTR_MODEL_DIR, 'dextr.bin')) - self._input_blob = next(iter(self._network.inputs)) - self._output_blob = next(iter(self._network.outputs)) - if getattr(self._plugin, 'load_network', False): - self._exec_network = self._plugin.load_network(self._network, 'CPU') - else: - self._exec_network = self._plugin.load(network=self._network) - - frame_provider = FrameProvider(db_data) - image = frame_provider.get_frame(frame, frame_provider.Quality.ORIGINAL) - image = PIL.Image.open(image[0]) - numpy_image = np.array(image) - points = np.asarray([[int(p["x"]), int(p["y"])] for p in points], dtype=int) - - # Padding mustn't be more than the closest distance to an edge of an image - [height, width] = numpy_image.shape[:2] - x_values = points[:, 0] - y_values = points[:, 1] - [min_x, max_x] = [np.min(x_values), np.max(x_values)] - [min_y, max_y] = [np.min(y_values), np.max(y_values)] - padding = min(min_x, min_y, width - max_x, height - max_y, _DEXTR_PADDING) - bounding_box = ( - max(min(points[:, 0]) - padding, 0), - max(min(points[:, 1]) - padding, 0), - min(max(points[:, 0]) + padding, width - 1), - min(max(points[:, 1]) + padding, height - 1) - ) - - # Prepare an image - numpy_cropped = np.array(image.crop(bounding_box)) - resized = cv2.resize(numpy_cropped, (_DEXTR_SIZE, _DEXTR_SIZE), - interpolation = cv2.INTER_CUBIC).astype(np.float32) - - # Make a heatmap - points = points - [min(points[:, 0]), min(points[:, 1])] + [padding, padding] - points = (points * [_DEXTR_SIZE / numpy_cropped.shape[1], _DEXTR_SIZE / numpy_cropped.shape[0]]).astype(int) - heatmap = np.zeros(shape=resized.shape[:2], dtype=np.float64) - for point in points: - gaussian_x_axis = np.arange(0, _DEXTR_SIZE, 1, float) - point[0] - gaussian_y_axis = np.arange(0, _DEXTR_SIZE, 1, float)[:, np.newaxis] - point[1] - gaussian = np.exp(-4 * np.log(2) * ((gaussian_x_axis ** 2 + gaussian_y_axis ** 2) / 100)).astype(np.float64) - heatmap = np.maximum(heatmap, gaussian) - cv2.normalize(heatmap, heatmap, 0, 255, cv2.NORM_MINMAX) - - # Concat an image and a heatmap - input_dextr = np.concatenate((resized, heatmap[:, :, np.newaxis].astype(resized.dtype)), axis=2) - input_dextr = input_dextr.transpose((2,0,1)) - - pred = self._exec_network.infer(inputs={self._input_blob: input_dextr[np.newaxis, ...]})[self._output_blob][0, 0, :, :] - pred = cv2.resize(pred, tuple(reversed(numpy_cropped.shape[:2])), interpolation = cv2.INTER_CUBIC) - result = np.zeros(numpy_image.shape[:2]) - result[bounding_box[1]:bounding_box[1] + pred.shape[0], bounding_box[0]:bounding_box[0] + pred.shape[1]] = pred > _DEXTR_TRESHOLD - - # Convert a mask to a polygon - result = np.array(result, dtype=np.uint8) - cv2.normalize(result,result,0,255,cv2.NORM_MINMAX) - contours = None - if int(cv2.__version__.split('.')[0]) > 3: - contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[0] - else: - contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[1] - - contours = max(contours, key=lambda arr: arr.size) - if contours.shape.count(1): - contours = np.squeeze(contours) - if contours.size < 3 * 2: - raise Exception('Less then three point have been detected. Can not build a polygon.') - - result = "" - for point in contours: - result += "{},{} ".format(int(point[0]), int(point[1])) - result = result[:-1] - - return result - - def __del__(self): - if self._exec_network: - del self._exec_network - if self._network: - del self._network - if self._plugin: - del self._plugin diff --git a/cvat/apps/dextr_segmentation/docker-compose.dextr.yml b/cvat/apps/dextr_segmentation/docker-compose.dextr.yml deleted file mode 100644 index 468523b8a05a..000000000000 --- a/cvat/apps/dextr_segmentation/docker-compose.dextr.yml +++ /dev/null @@ -1,14 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# - -version: "2.3" - -services: - cvat: - build: - context: . - args: - WITH_DEXTR: "yes" diff --git a/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js b/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js deleted file mode 100644 index 0869ba8df048..000000000000 --- a/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -/* global - AREA_TRESHOLD:false - PolyShapeModel:false - ShapeCreatorModel:true - ShapeCreatorView:true - showMessage:false -*/ - -/* eslint no-underscore-dangle: 0 */ - -window.addEventListener('DOMContentLoaded', () => { - $('').appendTo('#shapeTypeSelector'); - - const dextrCancelButtonId = 'dextrCancelButton'; - const dextrOverlay = $(` - `).appendTo('body'); - - const dextrCancelButton = $(`#${dextrCancelButtonId}`); - dextrCancelButton.on('click', () => { - dextrCancelButton.prop('disabled', true); - $.ajax({ - url: `/dextr/cancel/${window.cvat.job.id}`, - type: 'GET', - error: (errorData) => { - const message = `Can not cancel segmentation. Code: ${errorData.status}. - Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }, - complete: () => { - dextrCancelButton.prop('disabled', false); - }, - }); - }); - - function ShapeCreatorModelWrapper(OriginalClass) { - // Constructor will patch some properties for a created instance - function constructorDecorator(...args) { - const instance = new OriginalClass(...args); - - // Decorator for the defaultType property - Object.defineProperty(instance, 'defaultType', { - get: () => instance._defaultType, - set: (type) => { - if (!['box', 'box_by_4_points', 'points', 'polygon', - 'polyline', 'auto_segmentation', 'cuboid'].includes(type)) { - throw Error(`Unknown shape type found ${type}`); - } - instance._defaultType = type; - }, - }); - - // Decorator for finish method. - const decoratedFinish = instance.finish; - instance.finish = (result) => { - if (instance._defaultType === 'auto_segmentation') { - try { - instance._defaultType = 'polygon'; - decoratedFinish.call(instance, result); - } finally { - instance._defaultType = 'auto_segmentation'; - } - } else { - decoratedFinish.call(instance, result); - } - }; - - return instance; - } - - constructorDecorator.prototype = OriginalClass.prototype; - constructorDecorator.prototype.constructor = constructorDecorator; - return constructorDecorator; - } - - - function ShapeCreatorViewWrapper(OriginalClass) { - // Constructor will patch some properties for each instance - function constructorDecorator(...args) { - const instance = new OriginalClass(...args); - - // Decorator for the _create() method. - // We save the decorated _create() and we will use it if type != 'auto_segmentation' - const decoratedCreate = instance._create; - instance._create = () => { - if (instance._type !== 'auto_segmentation') { - decoratedCreate.call(instance); - return; - } - - instance._drawInstance = instance._frameContent.polyline().draw({ snapToGrid: 0.1 }).addClass('shapeCreation').attr({ - 'stroke-width': 0, - z_order: Number.MAX_SAFE_INTEGER, - }); - instance._createPolyEvents(); - - /* the _createPolyEvents method have added "drawdone" - * event handler which invalid for this case - * because of that reason we remove the handler and - * create the valid handler instead - */ - instance._drawInstance.off('drawdone').on('drawdone', (e) => { - let actualPoints = window.cvat.translate.points.canvasToActual(e.target.getAttribute('points')); - actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints); - - if (actualPoints.length < 4) { - showMessage('It is need to specify minimum four extreme points for an object'); - instance._controller.switchCreateMode(true); - return; - } - - const { frameWidth } = window.cvat.player.geometry; - const { frameHeight } = window.cvat.player.geometry; - for (let idx = 0; idx < actualPoints.length; idx += 1) { - const point = actualPoints[idx]; - point.x = Math.clamp(point.x, 0, frameWidth); - point.y = Math.clamp(point.y, 0, frameHeight); - } - - e.target.setAttribute('points', - window.cvat.translate.points.actualToCanvas( - PolyShapeModel.convertNumberArrayToString(actualPoints), - )); - - const polybox = e.target.getBBox(); - const area = polybox.width * polybox.height; - - if (area > AREA_TRESHOLD) { - $.ajax({ - url: `/dextr/create/${window.cvat.job.id}`, - type: 'POST', - data: JSON.stringify({ - frame: window.cvat.player.frames.current, - points: actualPoints, - }), - contentType: 'application/json', - success: () => { - function intervalCallback() { - $.ajax({ - url: `/dextr/check/${window.cvat.job.id}`, - type: 'GET', - success: (jobData) => { - if (['queued', 'started'].includes(jobData.status)) { - if (jobData.status === 'queued') { - dextrCancelButton.prop('disabled', false); - } - setTimeout(intervalCallback, 1000); - } else { - dextrOverlay.addClass('hidden'); - if (jobData.status === 'finished') { - if (jobData.result) { - instance._controller.finish({ points: jobData.result }, 'polygon'); - } - } else if (jobData.status === 'failed') { - const message = `Segmentation has fallen. Error: '${jobData.stderr}'`; - showMessage(message); - } else { - let message = `Check segmentation request returned "${jobData.status}" status.`; - if (jobData.stderr) { - message += ` Error: ${jobData.stderr}`; - } - showMessage(message); - } - } - }, - error: (errorData) => { - dextrOverlay.addClass('hidden'); - const message = `Can not check segmentation. Code: ${errorData.status}.` - + ` Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }, - }); - } - - dextrCancelButton.prop('disabled', true); - dextrOverlay.removeClass('hidden'); - setTimeout(intervalCallback, 1000); - }, - error: (errorData) => { - const message = `Can not cancel ReID process. Code: ${errorData.status}.` - + ` Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - }, - }); - } - - instance._controller.switchCreateMode(true); - }); // end of "drawdone" handler - }; // end of _create() method - - return instance; - } // end of constructorDecorator() - - constructorDecorator.prototype = OriginalClass.prototype; - constructorDecorator.prototype.constructor = constructorDecorator; - return constructorDecorator; - } // end of ShapeCreatorViewWrapper - - // Apply patch for classes - ShapeCreatorModel = ShapeCreatorModelWrapper(ShapeCreatorModel); - ShapeCreatorView = ShapeCreatorViewWrapper(ShapeCreatorView); -}); diff --git a/cvat/apps/dextr_segmentation/urls.py b/cvat/apps/dextr_segmentation/urls.py deleted file mode 100644 index 6b3120b67939..000000000000 --- a/cvat/apps/dextr_segmentation/urls.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('create/', views.create), - path('cancel/', views.cancel), - path('check/', views.check), - path('enabled', views.enabled) -] diff --git a/cvat/apps/dextr_segmentation/views.py b/cvat/apps/dextr_segmentation/views.py deleted file mode 100644 index dd78a2b01e42..000000000000 --- a/cvat/apps/dextr_segmentation/views.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse -from cvat.apps.authentication.decorators import login_required -from rules.contrib.views import permission_required, objectgetter - -from cvat.apps.engine.models import Job -from cvat.apps.engine.log import slogger -from cvat.apps.dextr_segmentation.dextr import DEXTR_HANDLER - -import django_rq -import json -import rq - -__RQ_QUEUE_NAME = "default" -__DEXTR_HANDLER = DEXTR_HANDLER() - -def _dextr_thread(db_data, frame, points): - job = rq.get_current_job() - job.meta["result"] = __DEXTR_HANDLER.handle(db_data, frame, points) - job.save_meta() - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, "jid"), raise_exception=True) -def create(request, jid): - try: - data = json.loads(request.body.decode("utf-8")) - - points = data["points"] - frame = int(data["frame"]) - username = request.user.username - - slogger.job[jid].info("create dextr request for the JOB: {} ".format(jid) - + "by the USER: {} on the FRAME: {}".format(username, frame)) - - db_data = Job.objects.select_related("segment__task__data").get(id=jid).segment.task.data - - queue = django_rq.get_queue(__RQ_QUEUE_NAME) - rq_id = "dextr.create/{}/{}".format(jid, username) - job = queue.fetch_job(rq_id) - - if job is not None and (job.is_started or job.is_queued): - if "cancel" not in job.meta: - raise Exception("Segmentation process has been already run for the " + - "JOB: {} and the USER: {}".format(jid, username)) - else: - job.delete() - - queue.enqueue_call(func=_dextr_thread, - args=(db_data, frame, points), - job_id=rq_id, - timeout=15, - ttl=30) - - return HttpResponse() - except Exception as ex: - slogger.job[jid].error("can't create a dextr request for the job {}".format(jid), exc_info=True) - return HttpResponseBadRequest(str(ex)) - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, "jid"), raise_exception=True) -def cancel(request, jid): - try: - username = request.user.username - slogger.job[jid].info("cancel dextr request for the JOB: {} ".format(jid) - + "by the USER: {}".format(username)) - - queue = django_rq.get_queue(__RQ_QUEUE_NAME) - rq_id = "dextr.create/{}/{}".format(jid, username) - job = queue.fetch_job(rq_id) - - if job is None or job.is_finished or job.is_failed: - raise Exception("Segmentation isn't running now") - elif "cancel" not in job.meta: - job.meta["cancel"] = True - job.save_meta() - - return HttpResponse() - except Exception as ex: - slogger.job[jid].error("can't cancel a dextr request for the job {}".format(jid), exc_info=True) - return HttpResponseBadRequest(str(ex)) - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, "jid"), raise_exception=True) -def check(request, jid): - try: - username = request.user.username - slogger.job[jid].info("check dextr request for the JOB: {} ".format(jid) - + "by the USER: {}".format(username)) - - queue = django_rq.get_queue(__RQ_QUEUE_NAME) - rq_id = "dextr.create/{}/{}".format(jid, username) - job = queue.fetch_job(rq_id) - data = {} - - if job is None: - data["status"] = "unknown" - else: - if "cancel" in job.meta: - data["status"] = "finished" - elif job.is_queued: - data["status"] = "queued" - elif job.is_started: - data["status"] = "started" - elif job.is_finished: - data["status"] = "finished" - data["result"] = job.meta["result"] - job.delete() - else: - data["status"] = "failed" - data["stderr"] = job.exc_info - job.delete() - - return JsonResponse(data) - except Exception as ex: - slogger.job[jid].error("can't check a dextr request for the job {}".format(jid), exc_info=True) - return HttpResponseBadRequest(str(ex)) - -def enabled(request): - return HttpResponse() diff --git a/cvat/apps/documentation/installation.md b/cvat/apps/documentation/installation.md index ec59168e2894..4720fb444e02 100644 --- a/cvat/apps/documentation/installation.md +++ b/cvat/apps/documentation/installation.md @@ -7,7 +7,14 @@ - [Stop all containers](#stop-all-containers) - [Advanced settings](#advanced-settings) - [Share path](#share-path) - - [Serving over HTTPS](#serving-over-https) + - [Serving over HTTPS](#serving-over-https) + - [Prerequisites](#prerequisites) + - [Roadmap](#roadmap) + - [Step-by-step instructions](#step-by-step-instructions) + - [1. Move the CVAT access port](#1-move-the-cvat-access-port) + - [2. Configure Nginx for the ACME challenge](#2-configure-nginx-for-the-acme-challenge) + - [3. Create certificate files using an ACME challenge](#3-create-certificate-files-using-an-acme-challenge) + - [4. Reconfigure Nginx for HTTPS access](#4-reconfigure-nginx-for-https-access) # Quick installation guide @@ -240,7 +247,6 @@ server. Proxy is an advanced topic and it is not covered by the guide. - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) - [TF Object Detection API: auto annotation](/components/tf_annotation/README.md) - [Support for NVIDIA GPUs](/components/cuda/README.md) -- [Semi-automatic segmentation with Deep Extreme Cut](/cvat/apps/dextr_segmentation/README.md) - [Auto segmentation: Keras+Tensorflow Mask R-CNN Segmentation](/components/auto_segmentation/README.md) ```bash @@ -311,16 +317,16 @@ contains a text (url for example) which is shown in the client-share browser. ### Serving over HTTPS We will add [letsencrypt.org](https://letsencrypt.org/) issued certificate to secure -our server connection. +our server connection. #### Prerequisites -We assume that +We assume that -- you have sudo access on your server machine, +- you have sudo access on your server machine, - you have an IP address to use for remote access, and - that the local CVAT installation works on your server. - + If this is not the case, please complete the steps in the installation manual first. #### Roadmap @@ -336,7 +342,7 @@ We will go through the following sequence of steps to get CVAT over HTTPS: ##### 1. Move the CVAT access port -Let's assume the server will be at `my-cvat-server.org`. +Let's assume the server will be at `my-cvat-server.org`. ```bash # on the server @@ -351,7 +357,7 @@ Add the following into your `docker-compose.override.yml`, replacing `my-cvat-se This file lives in the same directory as `docker-compose.yml`. ```yaml -# docker-compose.override.yml +# docker-compose.override.yml version: "2.3" services: @@ -360,7 +366,7 @@ services: CVAT_HOST: my-cvat-server.org ports: - "80:80" - + cvat: environment: ALLOWED_HOSTS: '*' @@ -370,8 +376,8 @@ You should now see an unsecured version of CVAT at `http://my-cvat-server.org`. ##### 2. Configure Nginx for the ACME challenge -Temporarily, enable serving `http://my-cvat-server.org/.well-known/acme-challenge/` -route from `/letsencrypt` directory on the server's filesystem. +Temporarily, enable serving `http://my-cvat-server.org/.well-known/acme-challenge/` +route from `/letsencrypt` directory on the server's filesystem. You can use the [Nginx quickstart guide](http://nginx.org/en/docs/beginners_guide.html) for reference. ```bash @@ -393,7 +399,7 @@ server { root /letsencrypt; } - location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|dextr/.*|reid/.* { + location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|reid/.* { proxy_pass http://cvat:8080; proxy_pass_header X-CSRFToken; proxy_set_header Host $http_host; @@ -426,7 +432,7 @@ Now create the `/letsencrypt` directory and mount it into `cvat_proxy` container Edit your `docker-compose.override.yml` to look like the following: ```yaml -# docker-compose.override.yml +# docker-compose.override.yml version: "2.3" services: @@ -437,7 +443,7 @@ services: - "80:80" volumes: - ./letsencrypt:/letsencrypt - + cvat: environment: ALLOWED_HOSTS: '*' @@ -453,7 +459,7 @@ docker-compose down docker-compose up -d ``` -Your server should still be visible (and unsecured) at `http://my-cvat-server.org` +Your server should still be visible (and unsecured) at `http://my-cvat-server.org` but you won't see any behavior changes. ##### 3. Create certificate files using an ACME challenge @@ -488,10 +494,10 @@ admin@tempVM:~/cvat$ docker exec -ti cvat_proxy /bin/sh [Fri Apr 3 20:49:06 UTC 2020] ACCOUNT_THUMBPRINT='tril8-LdJgM8xg6mnN1pMa7vIMdFizVCE0NImNmyZY4' [Fri Apr 3 20:49:06 UTC 2020] Creating domain key [ ... many more lines ...] -[Fri Apr 3 20:49:10 UTC 2020] Your cert is in /root/.acme.sh/my-cvat-server.org/my-cvat-server.org.cer -[Fri Apr 3 20:49:10 UTC 2020] Your cert key is in /root/.acme.sh/my-cvat-server.org/my-cvat-server.org.key -[Fri Apr 3 20:49:10 UTC 2020] The intermediate CA cert is in /root/.acme.sh/my-cvat-server.org/ca.cer -[Fri Apr 3 20:49:10 UTC 2020] And the full chain certs is there: /root/.acme.sh/my-cvat-server.org/fullchain.cer +[Fri Apr 3 20:49:10 UTC 2020] Your cert is in /root/.acme.sh/my-cvat-server.org/my-cvat-server.org.cer +[Fri Apr 3 20:49:10 UTC 2020] Your cert key is in /root/.acme.sh/my-cvat-server.org/my-cvat-server.org.key +[Fri Apr 3 20:49:10 UTC 2020] The intermediate CA cert is in /root/.acme.sh/my-cvat-server.org/ca.cer +[Fri Apr 3 20:49:10 UTC 2020] And the full chain certs is there: /root/.acme.sh/my-cvat-server.org/fullchain.cer / # cp ~/.acme.sh/my-cvat-server.org/my-cvat-server.org.cer /letsencrypt/certificate.cer / # cp ~/.acme.sh/my-cvat-server.org/my-cvat-server.org.key /letsencrypt/certificate.key @@ -521,7 +527,7 @@ services: volumes: - ./letsencrypt:/letsencrypt - ./cert:/cert:ro # this is new - + cvat: environment: ALLOWED_HOSTS: '*' @@ -547,7 +553,7 @@ server { ssl_certificate /cert/certificate.cer; ssl_certificate_key /cert/certificate.key; - location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|dextr/.*|reid/.* { + location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|reid/.* { proxy_pass http://cvat:8080; } diff --git a/cvat/apps/lambda_manager/urls.py b/cvat/apps/lambda_manager/urls.py index 603a5aec44de..aac96218cc5b 100644 --- a/cvat/apps/lambda_manager/urls.py +++ b/cvat/apps/lambda_manager/urls.py @@ -12,10 +12,8 @@ router.register('requests', views.RequestViewSet) # GET /api/v1/lambda/functions - get list of functions -# POST /api/v1/lambda/functions - add one more function # GET /api/v1/lambda/functions/ - get information about the function -# DEL /api/v1/lambda/functions/ - delete a function -# POST /api/v1/labmda/online-requests - call a function +# POST /api/v1/labmda/requests - call a function # { "function": "", "mode": "online|offline", "job": "", "frame": "", # "points": [...], } # GET /api/v1/lambda/requests - get list of requests diff --git a/cvat/settings/base.py b/cvat/settings/base.py index d330f6dd7822..2e55f46df954 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -162,9 +162,6 @@ def generate_ssh_keys(): if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no') and os.environ.get('REID_MODEL_DIR', ''): INSTALLED_APPS += ['cvat.apps.reid'] -if 'yes' == os.environ.get('WITH_DEXTR', 'no'): - INSTALLED_APPS += ['cvat.apps.dextr_segmentation'] - if os.getenv('DJANGO_LOG_VIEWER_HOST'): INSTALLED_APPS += ['cvat.apps.log_viewer'] diff --git a/cvat/urls.py b/cvat/urls.py index 6ae59f6b03aa..0f5d40c4d437 100644 --- a/cvat/urls.py +++ b/cvat/urls.py @@ -43,9 +43,6 @@ if apps.is_installed('cvat.apps.auto_annotation'): urlpatterns.append(path('auto_annotation/', include('cvat.apps.auto_annotation.urls'))) -if apps.is_installed('cvat.apps.dextr_segmentation'): - urlpatterns.append(path('dextr/', include('cvat.apps.dextr_segmentation.urls'))) - if apps.is_installed('cvat.apps.log_viewer'): urlpatterns.append(path('analytics/', include('cvat.apps.log_viewer.urls'))) diff --git a/cvat_proxy/conf.d/cvat.conf.template b/cvat_proxy/conf.d/cvat.conf.template index 084ce189ea7a..8e20750866a8 100644 --- a/cvat_proxy/conf.d/cvat.conf.template +++ b/cvat_proxy/conf.d/cvat.conf.template @@ -12,7 +12,7 @@ server { proxy_set_header Host $http_host; proxy_pass_header Set-Cookie; - location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|dextr/.*|reid/.* { + location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|reid/.* { proxy_pass http://cvat:8080; } From fd06913c645585e5268207a5fc99ae8d6ffca9a3 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 18 May 2020 11:12:03 +0300 Subject: [PATCH 15/98] Added types for each function (detector and interactor) --- docker-compose.yml | 1 + serverless/dextr/nuclio/function.yaml | 8 +++++--- serverless/dextr/nuclio/input.json | 4 ---- serverless/dextr/nuclio/requirements.txt | 1 - serverless/dextr/nuclio/run.sh | 3 --- .../faster_rcnn_inception_v2_coco/nuclio/function.yaml | 2 ++ .../nuclio/function.yaml | 2 ++ .../public/yolov-v3-tf/nuclio/function.yaml | 2 ++ 8 files changed, 12 insertions(+), 11 deletions(-) delete mode 100644 serverless/dextr/nuclio/input.json delete mode 100644 serverless/dextr/nuclio/requirements.txt delete mode 100755 serverless/dextr/nuclio/run.sh diff --git a/docker-compose.yml b/docker-compose.yml index 4d5501e234e0..7d37529da1cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -98,6 +98,7 @@ services: serverless: container_name: nuclio image: quay.io/nuclio/dashboard:stable-amd64 + restart: always networks: default: aliases: diff --git a/serverless/dextr/nuclio/function.yaml b/serverless/dextr/nuclio/function.yaml index 257d8617ef31..4a2bc0836c64 100644 --- a/serverless/dextr/nuclio/function.yaml +++ b/serverless/dextr/nuclio/function.yaml @@ -1,6 +1,8 @@ metadata: - name: dextr + name: public.dextr namespace: cvat + labels: + type: interactor spec: description: Deep Extreme Cut @@ -12,7 +14,7 @@ spec: value: /opt/nuclio/python3 build: - image: cvat/dextr + image: cvat/public.dextr baseImage: openvino/ubuntu18_runtime:2020.2 directives: @@ -30,7 +32,7 @@ spec: - kind: RUN value: unzip dextr_model_v1.zip - kind: RUN - value: pip3 install -r requirements.txt + value: pip3 install Pillow - kind: USER value: openvino diff --git a/serverless/dextr/nuclio/input.json b/serverless/dextr/nuclio/input.json deleted file mode 100644 index 51910035dd7c..000000000000 --- a/serverless/dextr/nuclio/input.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "points": [[1,1], [100,1], [100,100], [1,100]], - "image": "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" -} diff --git a/serverless/dextr/nuclio/requirements.txt b/serverless/dextr/nuclio/requirements.txt deleted file mode 100644 index 5873a2224bf9..000000000000 --- a/serverless/dextr/nuclio/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Pillow \ No newline at end of file diff --git a/serverless/dextr/nuclio/run.sh b/serverless/dextr/nuclio/run.sh deleted file mode 100755 index 841ff037573a..000000000000 --- a/serverless/dextr/nuclio/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -http POST http://localhost:53462 < ./input.json diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml index 81d3ff494858..f52ef9eee8d1 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -1,6 +1,8 @@ metadata: name: omz.public.faster_rcnn_inception_v2_coco namespace: cvat + labels: + type: detector spec: description: Faster RCNN inception v2 COCO from Intel Open Model Zoo diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml index 0febfca25fcf..f86e3ddaa96f 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -1,6 +1,8 @@ metadata: name: omz.public.mask_rcnn_inception_resnet_v2_atrous_coco namespace: cvat + labels: + type: detector spec: description: Mask RCNN inception resnet v2 COCO from Intel Open Model Zoo diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml index 52e383285911..9bd40b1f3696 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml @@ -1,6 +1,8 @@ metadata: name: omz.public.yolo-v3-tf namespace: cvat + labels: + type: detector spec: description: YOLO v3 from Intel Open Model Zoo From d79646a351875d903600862ceaeb2aff4a060437 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Wed, 20 May 2020 16:25:53 +0300 Subject: [PATCH 16/98] Initial version of lambda_manager. --- cvat/apps/lambda_manager/views.py | 112 +++++++++++++++++++++++++++++- docker-compose.yml | 2 + 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index ce9477584b49..23274e213c82 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -1,7 +1,113 @@ from django.shortcuts import render -from rest_framework import viewsets +from rest_framework import viewsets, status +from rest_framework.response import Response +import requests +import django_rq -# Create your views here. +NUCLIO_GATEWAY = 'http://serverless:8070/api/functions' +NUCLIO_HEADERS = {'x-nuclio-project-name': 'cvat'} +NUCLIO_TIMEOUT = 10 class FunctionViewSet(viewsets.ViewSet): - pass \ No newline at end of file + def list(self, request): + response = [] + try: + reply = requests.get(NUCLIO_GATEWAY, headers=NUCLIO_HEADERS, + timeout=NUCLIO_TIMEOUT) + reply.raise_for_status() + output = reply.json() + for name in output: + response.append(_extract_function_info(output[name])) + except requests.RequestException as err: + return Response(str(err), status=reply.status_code) + + return Response(data=response) + + + def retrieve(self, request, name): + try: + reply = requests.get(NUCLIO_GATEWAY + '/' + name, + headers=NUCLIO_HEADERS, timeout=NUCLIO_TIMEOUT) + reply.raise_for_status() + output = reply.json() + response = self._extract_function_info(output) + except requests.RequestException as err: + return Response(str(err), status=reply.status_code) + + return Response(data=response) + + @retrieve.mapping.post + def call(self, request, name): + try: + tid = request.data['task'] + frame = request.data['frame'] + extra = request.data.get('extra') + data = { + 'image': _get_image(tid, frame), + 'extra': extra + } + reply = requests.post(NUCLIO_GATEWAY + '/' + name, + headers=NUCLIO_HEADERS, json=data, timeout=NUCLIO_TIMEOUT) + reply.raise_for_status() + + # TODO: validate output of a function (detector, tracker, etc...) + response = reply.json() + except requests.RequestException as err: + return Response(str(err), status=reply.status_code) + + return Response(data=response) + + @staticmethod + def _get_image(jid, frame): + pass + + @staticmethod + def _extract_function_info(data): + return { + 'name': data['metadata']['name'], + 'kind': data['metadata']['labels'].get('type'), + 'state': data['status']['state'], + 'description': data['spec']['description'] + } + +class RequestViewSet(viewsets.ViewSet): + def list(self, request): + pass + + def create(self, request): + pass + + def retrieve(self, request, pk): + pass + + def delete(self, request, pk): + pass + + +# # { 'function': 'name', 'task': 'id', 'frames': { 'start': 0, 'stop': 10 } } +# # { 'function': 'name', 'job': 'id', 'frames': { 'start': 0, 'stop': 10 } } +# def create_request(request): +# try: +# params = request.data +# queue = django_rq.get_queue("low") +# func_name = params['function'] +# tid = params.get('task') +# jid = params.get('job') +# frames = params.get('frames') + + +# except: +# pass + +# def get_requests(): +# queue = django_rq.get_queue("low") +# response = [] +# for job in queue.jobs: +# if job.func_name.starts_with("labmda"): +# response.append({ +# "id": job.get_id(), +# "" +# }) + + + diff --git a/docker-compose.yml b/docker-compose.yml index 7d37529da1cb..9fbe1f2de504 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -106,6 +106,8 @@ services: volumes: - /tmp:/tmp - /var/run/docker.sock:/var/run/docker.sock + ports: + - "8070:8070" volumes: cvat_db: From 4ec9da937f42a6874c7bc640340c887d89c09a1d Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Wed, 20 May 2020 19:06:47 +0300 Subject: [PATCH 17/98] Implement a couple of methods for lambda: GET /api/v1/lambda/functions GET /api/v1/lambda/functions/public.dextr --- cvat/apps/lambda_manager/urls.py | 11 ++++++++--- cvat/apps/lambda_manager/views.py | 16 +++++++++------- cvat/settings/base.py | 1 + cvat/urls.py | 3 +++ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/cvat/apps/lambda_manager/urls.py b/cvat/apps/lambda_manager/urls.py index aac96218cc5b..0225db53731a 100644 --- a/cvat/apps/lambda_manager/urls.py +++ b/cvat/apps/lambda_manager/urls.py @@ -8,8 +8,13 @@ from . import views router = routers.DefaultRouter(trailing_slash=False) -router.register('functions', views.FunctionViewSet) -router.register('requests', views.RequestViewSet) +# https://github.com/encode/django-rest-framework/issues/6645 +# I want to "call" my functions. To do that need to map my call method to +# POST (like get HTTP method is mapped to list(...)). One way is to implement +# own CustomRouter. But it is simpler just patch the router instance here. +router.routes[2].mapping.update({'post': 'call'}) +router.register('functions', views.FunctionViewSet, basename='function') +router.register('requests', views.RequestViewSet, basename='request') # GET /api/v1/lambda/functions - get list of functions # GET /api/v1/lambda/functions/ - get information about the function @@ -20,5 +25,5 @@ # GET /api/v1/lambda/requests/ - get status of the request # DEL /api/v1/lambda/requests/ - cancel a request (don't delete) urlpatterns = [ - path('api/v1/lambda', include((router.urls, 'cvat'), namespace='v1')) + path('api/v1/lambda/', include((router.urls, 'cvat'), namespace='v1')) ] \ No newline at end of file diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 23274e213c82..d4ecb33982a5 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -4,11 +4,14 @@ import requests import django_rq -NUCLIO_GATEWAY = 'http://serverless:8070/api/functions' +# FIXME: need to define the host in settings +NUCLIO_GATEWAY = 'http://localhost:8070/api/functions' NUCLIO_HEADERS = {'x-nuclio-project-name': 'cvat'} NUCLIO_TIMEOUT = 10 class FunctionViewSet(viewsets.ViewSet): + lookup_value_regex = '[a-zA-Z0-9_.]+' + def list(self, request): response = [] try: @@ -17,16 +20,16 @@ def list(self, request): reply.raise_for_status() output = reply.json() for name in output: - response.append(_extract_function_info(output[name])) + response.append(self._extract_function_info(output[name])) except requests.RequestException as err: return Response(str(err), status=reply.status_code) return Response(data=response) - def retrieve(self, request, name): + def retrieve(self, request, pk): try: - reply = requests.get(NUCLIO_GATEWAY + '/' + name, + reply = requests.get(NUCLIO_GATEWAY + '/' + pk, headers=NUCLIO_HEADERS, timeout=NUCLIO_TIMEOUT) reply.raise_for_status() output = reply.json() @@ -36,8 +39,7 @@ def retrieve(self, request, name): return Response(data=response) - @retrieve.mapping.post - def call(self, request, name): + def call(self, request, pk): try: tid = request.data['task'] frame = request.data['frame'] @@ -46,7 +48,7 @@ def call(self, request, name): 'image': _get_image(tid, frame), 'extra': extra } - reply = requests.post(NUCLIO_GATEWAY + '/' + name, + reply = requests.post(NUCLIO_GATEWAY + '/' + pk, headers=NUCLIO_HEADERS, json=data, timeout=NUCLIO_TIMEOUT) reply.raise_for_status() diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 3f691525b144..e738437efadb 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -99,6 +99,7 @@ def generate_ssh_keys(): 'cvat.apps.engine', 'cvat.apps.git', 'cvat.apps.restrictions', + 'cvat.apps.lambda_manager', 'django_rq', 'compressor', 'cacheops', diff --git a/cvat/urls.py b/cvat/urls.py index 0f5d40c4d437..11a14d3af78a 100644 --- a/cvat/urls.py +++ b/cvat/urls.py @@ -46,6 +46,9 @@ if apps.is_installed('cvat.apps.log_viewer'): urlpatterns.append(path('analytics/', include('cvat.apps.log_viewer.urls'))) +if apps.is_installed('cvat.apps.lambda_manager'): + urlpatterns.append(path('', include('cvat.apps.lambda_manager.urls'))) + if apps.is_installed('silk'): urlpatterns.append(path('profiler/', include('silk.urls'))) From 666e71ca7472258b80759ce8b50bb2deb620c572 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sun, 31 May 2020 17:58:20 +0300 Subject: [PATCH 18/98] First working version of dextr serverless function --- cvat/apps/lambda_manager/views.py | 52 +++++++++++++++++++++---------- serverless/dextr/nuclio/main.py | 2 +- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index d4ecb33982a5..28d4433cbece 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -1,6 +1,10 @@ from django.shortcuts import render from rest_framework import viewsets, status from rest_framework.response import Response +from cvat.apps.engine.frame_provider import FrameProvider +from cvat.apps.engine.models import Task as TaskModel +import base64 +import json import requests import django_rq @@ -11,6 +15,7 @@ class FunctionViewSet(viewsets.ViewSet): lookup_value_regex = '[a-zA-Z0-9_.]+' + lookup_field = 'name' def list(self, request): response = [] @@ -26,30 +31,40 @@ def list(self, request): return Response(data=response) + @staticmethod + def _get_function(name): + reply = requests.get(NUCLIO_GATEWAY + '/' + name, + headers=NUCLIO_HEADERS, timeout=NUCLIO_TIMEOUT) + reply.raise_for_status() + output = reply.json() - def retrieve(self, request, pk): + return output + + + def retrieve(self, request, name): try: - reply = requests.get(NUCLIO_GATEWAY + '/' + pk, - headers=NUCLIO_HEADERS, timeout=NUCLIO_TIMEOUT) - reply.raise_for_status() - output = reply.json() + output = self._get_function(name) response = self._extract_function_info(output) except requests.RequestException as err: - return Response(str(err), status=reply.status_code) + return Response(str(err), status=output.status_code) return Response(data=response) - def call(self, request, pk): + def call(self, request, name): try: tid = request.data['task'] frame = request.data['frame'] - extra = request.data.get('extra') + reply = self._get_function(name) + port = reply["status"]["httpPort"] + + points = request.data.get('points') data = { - 'image': _get_image(tid, frame), - 'extra': extra + 'image': self._get_image(tid, frame), + 'points': points } - reply = requests.post(NUCLIO_GATEWAY + '/' + pk, - headers=NUCLIO_HEADERS, json=data, timeout=NUCLIO_TIMEOUT) + + reply = requests.post('http://localhost:{}'.format(port), + json=data, timeout=NUCLIO_TIMEOUT) reply.raise_for_status() # TODO: validate output of a function (detector, tracker, etc...) @@ -60,8 +75,14 @@ def call(self, request, pk): return Response(data=response) @staticmethod - def _get_image(jid, frame): - pass + def _get_image(tid, frame): + db_task = TaskModel.objects.get(pk=tid) + frame_provider = FrameProvider(db_task.data) + # FIXME: now we cannot use the original quality because nuclio has body + # limit size by default 4Mb (from FastHTTP). + image = frame_provider.get_frame(frame, quality=FrameProvider.Quality.COMPRESSED) + + return base64.b64encode(image[0].getvalue()).decode('utf-8') @staticmethod def _extract_function_info(data): @@ -110,6 +131,3 @@ def delete(self, request, pk): # "id": job.get_id(), # "" # }) - - - diff --git a/serverless/dextr/nuclio/main.py b/serverless/dextr/nuclio/main.py index 895c4b3bfbd7..1d1fa97c141f 100644 --- a/serverless/dextr/nuclio/main.py +++ b/serverless/dextr/nuclio/main.py @@ -16,7 +16,7 @@ def handler(context, event): context.logger.info("call handler") data = event.body points = data["points"] - buf = io.BytesIO(base64.b64decode(data["image"])) + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) image = Image.open(buf) polygon = context.user_data.dextr_handler.handle(image, points) From c1609410b98470cae12c48f1dbaf6c28e95ff6aa Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sun, 31 May 2020 22:41:51 +0300 Subject: [PATCH 19/98] First version of dextr which works in UI. --- cvat-ui/src/utils/dextr-utils.ts | 58 +++++---------------------- cvat-ui/src/utils/plugin-checker.ts | 2 +- serverless/dextr/nuclio/function.yaml | 8 ++++ 3 files changed, 20 insertions(+), 48 deletions(-) diff --git a/cvat-ui/src/utils/dextr-utils.ts b/cvat-ui/src/utils/dextr-utils.ts index 03280cface55..8a6d3a2ee292 100644 --- a/cvat-ui/src/utils/dextr-utils.ts +++ b/cvat-ui/src/utils/dextr-utils.ts @@ -71,20 +71,19 @@ antModal.append(antModalContent); antModalWrap.append(antModal); antModalRoot.append(antModalMask, antModalWrap); - function serverRequest( plugin: DEXTRPlugin, - jid: number, + tid: number, frame: number, points: number[], ): Promise { return new Promise((resolve, reject) => { - const reducer = (acc: Point[], _: number, index: number, array: number[]): Point[] => { + const reducer = (acc: number[][], _: number, index: number, array: number[]): number[][] => { if (!(index % 2)) { // 0, 2, 4 - acc.push({ - x: array[index], - y: array[index + 1], - }); + acc.push([ + array[index], + array[index + 1], + ]); } return acc; @@ -92,9 +91,10 @@ function serverRequest( const reducedPoints = points.reduce(reducer, []); core.server.request( - `${baseURL}/dextr/create/${jid}`, { + `${baseURL}/api/v1/lambda/functions/public.dextr`, { method: 'POST', data: JSON.stringify({ + task: tid, frame, points: reducedPoints, }), @@ -102,44 +102,8 @@ function serverRequest( 'Content-Type': 'application/json', }, }, - ).then(() => { - const timeoutCallback = (): void => { - core.server.request( - `${baseURL}/dextr/check/${jid}`, { - method: 'GET', - }, - ).then((response: any) => { - const { status } = response; - if (status === RQStatus.finished) { - resolve(response.result.split(/\s|,/).map((coord: string) => +coord)); - } else if (status === RQStatus.failed) { - reject(new Error(response.stderr)); - } else if (status === RQStatus.unknown) { - reject(new Error('Unknown DEXTR status has been received')); - } else { - if (status === RQStatus.queued) { - antModalButton.disabled = false; - } - if (!plugin.data.canceled) { - setTimeout(timeoutCallback, 1000); - } else { - core.server.request( - `${baseURL}/dextr/cancel/${jid}`, { - method: 'GET', - }, - ).then(() => { - resolve(points); - }).catch((error: Error) => { - reject(error); - }); - } - } - }).catch((error: Error) => { - reject(error); - }); - }; - - setTimeout(timeoutCallback, 1000); + ).then((response: any) => { + resolve(response.flat()); }).catch((error: Error) => { reject(error); }); @@ -160,7 +124,7 @@ async function enter(this: any, self: DEXTRPlugin, objects: any[]): Promise= 8) { promises[i] = serverRequest( self, - this.id, + this.task.id, objects[i].frame, objects[i].points, ); diff --git a/cvat-ui/src/utils/plugin-checker.ts b/cvat-ui/src/utils/plugin-checker.ts index 54113f9a52aa..2d244c250377 100644 --- a/cvat-ui/src/utils/plugin-checker.ts +++ b/cvat-ui/src/utils/plugin-checker.ts @@ -36,7 +36,7 @@ class PluginChecker { return isReachable(`${serverHost}/tensorflow/segmentation/meta/get`, 'OPTIONS'); } case SupportedPlugins.DEXTR_SEGMENTATION: { - return isReachable(`${serverHost}/dextr/enabled`, 'GET'); + return isReachable(`${serverHost}/api/v1/lambda/functions/public.dextr`, 'GET'); } case SupportedPlugins.ANALYTICS: { return isReachable(`${serverHost}/analytics/app/kibana`, 'GET'); diff --git a/serverless/dextr/nuclio/function.yaml b/serverless/dextr/nuclio/function.yaml index 4a2bc0836c64..f598002cb9c7 100644 --- a/serverless/dextr/nuclio/function.yaml +++ b/serverless/dextr/nuclio/function.yaml @@ -41,3 +41,11 @@ spec: maxWorkers: 2 kind: "http" workerAvailabilityTimeoutMilliseconds: 10000 + + platform: + attributes: + network: "cvat_default" + + restartPolicy: + name: always + maximumRetryCount: 3 From 86f9ab2adb89ed29d529e16e329eecc582d4a04f Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 1 Jun 2020 11:24:17 +0300 Subject: [PATCH 20/98] Modify omz.public.faster_rcnn_inception_v2_coco - image decoding - restart policy always for the function --- .../faster_rcnn_inception_v2_coco/nuclio/function.yaml | 8 ++++++++ .../public/faster_rcnn_inception_v2_coco/nuclio/main.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml index f52ef9eee8d1..1447ffd15e6a 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -39,3 +39,11 @@ spec: maxWorkers: 2 kind: "http" workerAvailabilityTimeoutMilliseconds: 10000 + + platform: + attributes: + network: "cvat_default" + + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py index feccc30dfa9e..341bfab60c97 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py @@ -99,7 +99,7 @@ def init_context(context): def handler(context, event): context.logger.info("Run faster_rcnn_inception_v2_coco model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"])) + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) threshold = float(data.get("threshold", 0.5)) image = Image.open(buf) From b7c4768b20a9ed4843b56fb0e279587bb1c7f61b Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 1 Jun 2020 14:37:56 +0300 Subject: [PATCH 21/98] Improve omz.public.mask_rcnn_inception_resnet_v2_atrous_coco --- .../nuclio/function.yaml | 11 +++++++++++ .../nuclio/main.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml index f86e3ddaa96f..0e27f71aa22f 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -1,3 +1,6 @@ +# To build the function you need to adjust docker settings. Be sure that you +# have enough memory (more than 4GB). Look here how to do that +# https://stackoverflow.com/questions/44417159/docker-process-killed-with-cryptic-killed-message metadata: name: omz.public.mask_rcnn_inception_resnet_v2_atrous_coco namespace: cvat @@ -43,3 +46,11 @@ spec: maxWorkers: 2 kind: "http" workerAvailabilityTimeoutMilliseconds: 10000 + + platform: + attributes: + network: "cvat_default" + + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py index 3f1f18e89566..2773dd621316 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py @@ -122,7 +122,7 @@ def segm_postprocess(box: list, raw_cls_mask, im_h, im_w): def handler(context, event): context.logger.info("Run mask_rcnn_inception_resnet_v2_atrous_coco model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"])) + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) threshold = float(data.get("threshold", 0.2)) image = Image.open(buf) From 6b232d635aaf88114d6e36c44be64c0a285afdd3 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 1 Jun 2020 14:56:43 +0300 Subject: [PATCH 22/98] Improve omz.public.yolo-v3-tf function --- cvat/apps/lambda_manager/views.py | 4 ++-- .../public/yolov-v3-tf/nuclio/function.yaml | 8 +++++++ .../public/yolov-v3-tf/nuclio/main.py | 21 ++++++++----------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 28d4433cbece..ee20935a4c8a 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -11,10 +11,10 @@ # FIXME: need to define the host in settings NUCLIO_GATEWAY = 'http://localhost:8070/api/functions' NUCLIO_HEADERS = {'x-nuclio-project-name': 'cvat'} -NUCLIO_TIMEOUT = 10 +NUCLIO_TIMEOUT = 60 class FunctionViewSet(viewsets.ViewSet): - lookup_value_regex = '[a-zA-Z0-9_.]+' + lookup_value_regex = '[a-zA-Z0-9_.-]+' lookup_field = 'name' def list(self, request): diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml index 9bd40b1f3696..af9bc9da6682 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml @@ -39,3 +39,11 @@ spec: maxWorkers: 2 kind: "http" workerAvailabilityTimeoutMilliseconds: 10000 + + platform: + attributes: + network: "cvat_default" + + restartPolicy: + name: always + maximumRetryCount: 3 \ No newline at end of file diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py index c519671b4921..d355ef99cecb 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py @@ -207,16 +207,14 @@ def intersection_over_union(box_1, box_2): def handler(context, event): context.logger.info("Run yolo-v3-tf model") data = event.body - buf = io.BytesIO(base64.b64decode(data["image"])) - threshold = float(data.get("threshold", 0.2)) + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.5)) image = Image.open(buf) output_layer = context.user_data.model_handler.infer(np.array(image)) - results = [] # Collecting object detection results objects = [] - PROB_THRESHOLD = 0.5 model = context.user_data.model_handler origin_im_size = (image.height, image.width) for layer_name, out_blob in output_layer.items(): @@ -224,22 +222,21 @@ def handler(context, event): layer_params = YoloParams(model.layers[layer_name].params, out_blob.shape[2]) objects += parse_yolo_region(out_blob, model.input_size(), origin_im_size, layer_params, - PROB_THRESHOLD) + threshold) - # Filtering overlapping boxes with respect to the --iou_threshold CLI parameter + # Filtering overlapping boxes (non-maximum supression) IOU_THRESHOLD = 0.4 objects = sorted(objects, key=lambda obj : obj['confidence'], reverse=True) - for i in range(len(objects)): - if objects[i]['confidence'] == 0: + for i, obj in enumerate(objects): + if obj['confidence'] == 0: continue for j in range(i + 1, len(objects)): - if intersection_over_union(objects[i], objects[j]) > IOU_THRESHOLD: + if intersection_over_union(obj, objects[j]) > IOU_THRESHOLD: objects[j]['confidence'] = 0 - objects = [obj for obj in objects if obj['confidence'] >= PROB_THRESHOLD] - + results = [] for obj in objects: - if obj['confidence'] >= PROB_THRESHOLD: + if obj['confidence'] >= threshold: xtl = max(obj['xmin'], 0) ytl = max(obj['ymin'], 0) xbr = min(obj['xmax'], image.width) From 737ccce76ab9db5290d8ac6088a2ea7007979391 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 1 Jun 2020 17:22:01 +0300 Subject: [PATCH 23/98] Implemented the initial version of requests for lambda manager. --- cvat/apps/lambda_manager/views.py | 120 +++++++++++++++++++----------- 1 file changed, 78 insertions(+), 42 deletions(-) diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index ee20935a4c8a..1ef000012a67 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -50,29 +50,35 @@ def retrieve(self, request, name): return Response(data=response) + @staticmethod + def _call(function, task_id, frame, points=None): + reply = FunctionViewSet._get_function(function) + port = reply["status"]["httpPort"] + + data = { + 'image': FunctionViewSet._get_image(task_id, frame), + 'points': points + } + + reply = requests.post('http://localhost:{}'.format(port), + json=data, timeout=NUCLIO_TIMEOUT) + reply.raise_for_status() + + # TODO: validate output of a function (detector, tracker, etc...) + return reply + + def call(self, request, name): try: tid = request.data['task'] frame = request.data['frame'] - reply = self._get_function(name) - port = reply["status"]["httpPort"] - points = request.data.get('points') - data = { - 'image': self._get_image(tid, frame), - 'points': points - } - - reply = requests.post('http://localhost:{}'.format(port), - json=data, timeout=NUCLIO_TIMEOUT) - reply.raise_for_status() - # TODO: validate output of a function (detector, tracker, etc...) - response = reply.json() + reply = self._call(function=name, task_id=tid, frame=frame, points=points) except requests.RequestException as err: return Response(str(err), status=reply.status_code) - return Response(data=response) + return Response(data=reply.json()) @staticmethod def _get_image(tid, frame): @@ -94,40 +100,70 @@ def _extract_function_info(data): } class RequestViewSet(viewsets.ViewSet): + QUEUE_NAME = 'low' + + @staticmethod + def _get_job(job): + return { + "id": job.id, + "function": { + "name": job.args[0], + "args": job.args[1:] + }, + "status": job.get_status(), + "enqueued": job.enqueued_at(), + "started": job.started_at(), + "ended": job.ended_at(), + "exc_info": job.exc_info + } + def list(self, request): + queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) + results = [] + for job in queue.jobs: + results.append(self._get_job(job)) + + return Response(data=results) + + @staticmethod + def _save_annotations(db_task, frame, annotations): pass + @staticmethod + def _call(function, threshold, task_id): + try: + db_task = TaskModel.objects.get(pk=task_id) + for frame in range(db_task.size): + reply = FunctionViewSet._call(function, task_id, frame) + RequestViewSet._save_annotations(db_task, frame, reply.json()) + except requests.RequestException as err: + return Response(str(err), status=reply.status_code) + + + # { 'function': 'name', 'threshold': 'n', 'task': 'id'} def create(self, request): - pass + function = request.data['function'] + threshold = request.data['threshold'] + task_id = request.data['task'] + + queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) + queue.enqueue(self._call, function, threshold, task_id) + def retrieve(self, request, pk): - pass + queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) + job = queue.fetch_job(pk) + if job != None: + return Response(data=self._get_job(job)) + else: + return Response(status.HTTP_404_NOT_FOUND) def delete(self, request, pk): - pass - + queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) + job = queue.fetch_job(pk) + if job != None: + job.delete() + return Response(status.HTTP_204_NO_CONTENT) + else: + return Response(status.HTTP_404_NOT_FOUND) -# # { 'function': 'name', 'task': 'id', 'frames': { 'start': 0, 'stop': 10 } } -# # { 'function': 'name', 'job': 'id', 'frames': { 'start': 0, 'stop': 10 } } -# def create_request(request): -# try: -# params = request.data -# queue = django_rq.get_queue("low") -# func_name = params['function'] -# tid = params.get('task') -# jid = params.get('job') -# frames = params.get('frames') - - -# except: -# pass - -# def get_requests(): -# queue = django_rq.get_queue("low") -# response = [] -# for job in queue.jobs: -# if job.func_name.starts_with("labmda"): -# response.append({ -# "id": job.get_id(), -# "" -# }) From 3b44879d938961f25f51ebb6e2e93adff38003e2 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Thu, 11 Jun 2020 10:59:07 +0300 Subject: [PATCH 24/98] First working version of POST /api/v1/lambda/requests --- cvat/apps/lambda_manager/views.py | 89 +++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 1ef000012a67..029124d3bdb8 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -3,10 +3,13 @@ from rest_framework.response import Response from cvat.apps.engine.frame_provider import FrameProvider from cvat.apps.engine.models import Task as TaskModel +from cvat.apps.dataset_manager.task import put_task_data, patch_task_data import base64 import json import requests import django_rq +import rq +from cvat.apps.engine.serializers import LabeledDataSerializer # FIXME: need to define the host in settings NUCLIO_GATEWAY = 'http://localhost:8070/api/functions' @@ -74,9 +77,10 @@ def call(self, request, name): frame = request.data['frame'] points = request.data.get('points') - reply = self._call(function=name, task_id=tid, frame=frame, points=points) + reply = FunctionViewSet._call(function=name, task_id=tid, frame=frame, points=points) except requests.RequestException as err: - return Response(str(err), status=reply.status_code) + # TODO: need to handle requests exception and return correct status + return Response(str(err), status=status.HTTP_400_BAD_REQUEST) return Response(data=reply.json()) @@ -99,6 +103,20 @@ def _extract_function_info(data): 'description': data['spec']['description'] } +def _callme(function, threshold, task_id): + try: + # TODO: need to remove annotations if clear flag is True + # TODO: need logging + db_task = TaskModel.objects.get(pk=task_id) + # TODO: check tasks with a frame step + for frame in range(db_task.data.size): + reply = FunctionViewSet._call(function, task_id, frame) + RequestViewSet._save_annotations(db_task, frame, reply.json()) + except requests.RequestException as err: + # TODO: handle exceptions (e.g. no task, etc) + return Response(str(err), status=status.HTTP_400_BAD_REQUEST) + + class RequestViewSet(viewsets.ViewSet): QUEUE_NAME = 'low' @@ -111,9 +129,9 @@ def _get_job(job): "args": job.args[1:] }, "status": job.get_status(), - "enqueued": job.enqueued_at(), - "started": job.started_at(), - "ended": job.ended_at(), + "enqueued": job.enqueued_at, + "started": job.started_at, + "ended": job.ended_at, "exc_info": job.exc_info } @@ -127,27 +145,56 @@ def list(self, request): @staticmethod def _save_annotations(db_task, frame, annotations): - pass - - @staticmethod - def _call(function, threshold, task_id): - try: - db_task = TaskModel.objects.get(pk=task_id) - for frame in range(db_task.size): - reply = FunctionViewSet._call(function, task_id, frame) - RequestViewSet._save_annotations(db_task, frame, reply.json()) - except requests.RequestException as err: - return Response(str(err), status=reply.status_code) - - - # { 'function': 'name', 'threshold': 'n', 'task': 'id'} + # TODO: optimize + # TODO: need user mapping between model labels and task labels + db_labels = db_task.label_set.prefetch_related("attributespec_set").all() + #attributes = {db_label.id: + # {db_attr.name: db_attr.id for db_attr in db_label.attributespec_set.all()} + # for db_label in db_labels} + labels = {db_label.name:db_label.id for db_label in db_labels} + + # TODO: need to check 'cancel' operation + job = rq.get_current_job() + job.meta["progress"] = frame / db_task.data.size + job.save_meta() + + + # TODO: need to make "tags" and "tracks" are optional + # FIXME: need to provide the correct version here + results = {"version": 0, "tags": [], "shapes": [], "tracks": []} + for anno in annotations: + label_id = labels.get(anno["label"]) + if label_id is not None: + results["shapes"].append({ + "frame": frame, + "label_id": label_id, + "type": anno["type"], + "occluded": False, + "points": anno["points"], + "z_order": 0, + "group": None, + "attributes": [] + }) + + serializer = LabeledDataSerializer(data=results) + # TODO: handle exceptions + if serializer.is_valid(raise_exception=True): + patch_task_data(db_task.id, results, "create") + + # { 'function': 'name', 'threshold': 't', 'task': 'id'} def create(self, request): function = request.data['function'] - threshold = request.data['threshold'] + threshold = request.data.get('threshold') task_id = request.data['task'] queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) - queue.enqueue(self._call, function, threshold, task_id) + # TODO: protect from running multiple times for the same task + # Only one job for an annotation task + job = queue.enqueue(_callme, function, threshold, task_id) + + + + return Response(data={"id": job.id}) def retrieve(self, request, pk): From 220d905f241679fc255bf84858616b6dd496430d Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Wed, 17 Jun 2020 13:39:36 +0300 Subject: [PATCH 25/98] Updated specification of function.yaml (added labels and used annotations section). --- cvat/requirements/development.txt | 4 +- serverless/dextr/nuclio/function.yaml | 3 +- .../nuclio/function.yaml | 85 +++++++++++++++++- .../nuclio/function.yaml | 85 +++++++++++++++++- .../public/yolov-v3-tf/nuclio/function.yaml | 87 ++++++++++++++++++- 5 files changed, 257 insertions(+), 7 deletions(-) diff --git a/cvat/requirements/development.txt b/cvat/requirements/development.txt index a83e15f3b39d..a1bcf8b63d37 100644 --- a/cvat/requirements/development.txt +++ b/cvat/requirements/development.txt @@ -1,11 +1,11 @@ -r base.txt astroid==2.4.2 isort==4.3.21 -lazy-object-proxy==1.3.1 +lazy-object-proxy==1.5.0 mccabe==0.6.1 pylint==2.3.1 pylint-django==2.0.15 -pylint-plugin-utils==0.2.6 +pylint-plugin-utils==0.6 rope==0.11 wrapt==1.12.1 django-extensions==2.0.6 diff --git a/serverless/dextr/nuclio/function.yaml b/serverless/dextr/nuclio/function.yaml index f598002cb9c7..6cd541963ded 100644 --- a/serverless/dextr/nuclio/function.yaml +++ b/serverless/dextr/nuclio/function.yaml @@ -1,8 +1,9 @@ metadata: name: public.dextr namespace: cvat - labels: + annotations: type: interactor + spec: spec: description: Deep Extreme Cut diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml index 1447ffd15e6a..dffb40c36c7d 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -1,8 +1,91 @@ metadata: name: omz.public.faster_rcnn_inception_v2_coco namespace: cvat - labels: + annotations: type: detector + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] spec: description: Faster RCNN inception v2 COCO from Intel Open Model Zoo diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml index 0e27f71aa22f..b9bc1651f942 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -4,8 +4,91 @@ metadata: name: omz.public.mask_rcnn_inception_resnet_v2_atrous_coco namespace: cvat - labels: + annotations: type: detector + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] spec: description: Mask RCNN inception resnet v2 COCO from Intel Open Model Zoo diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml index af9bc9da6682..cd868a9d4251 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml @@ -1,8 +1,91 @@ metadata: name: omz.public.yolo-v3-tf namespace: cvat - labels: + annotations: type: detector + spec: | + [ + { "id": 0, "name": "person" }, + { "id": 1, "name": "bicycle" }, + { "id": 2, "name": "car" }, + { "id": 3, "name": "motorbike" }, + { "id": 4, "name": "aeroplane" }, + { "id": 5, "name": "bus" }, + { "id": 6, "name": "train" }, + { "id": 7, "name": "truck" }, + { "id": 8, "name": "boat" }, + { "id": 9, "name": "traffic light" }, + { "id": 10, "name": "fire hydrant" }, + { "id": 11, "name": "stop sign" }, + { "id": 12, "name": "parking meter" }, + { "id": 13, "name": "bench" }, + { "id": 14, "name": "bird" }, + { "id": 15, "name": "cat" }, + { "id": 16, "name": "dog" }, + { "id": 17, "name": "horse" }, + { "id": 18, "name": "sheep" }, + { "id": 19, "name": "cow" }, + { "id": 20, "name": "elephant" }, + { "id": 21, "name": "bear" }, + { "id": 22, "name": "zebra" }, + { "id": 23, "name": "giraffe" }, + { "id": 24, "name": "backpack" }, + { "id": 25, "name": "umbrella" }, + { "id": 26, "name": "handbag" }, + { "id": 27, "name": "tie" }, + { "id": 28, "name": "suitcase" }, + { "id": 29, "name": "frisbee" }, + { "id": 30, "name": "skis" }, + { "id": 31, "name": "snowboard" }, + { "id": 32, "name": "sports ball" }, + { "id": 33, "name": "kite" }, + { "id": 34, "name": "baseball bat" }, + { "id": 35, "name": "baseball glove" }, + { "id": 36, "name": "skateboard" }, + { "id": 37, "name": "surfboard" }, + { "id": 38, "name": "tennis racket" }, + { "id": 39, "name": "bottle" }, + { "id": 40, "name": "wine glass" }, + { "id": 41, "name": "cup" }, + { "id": 42, "name": "fork" }, + { "id": 43, "name": "knife" }, + { "id": 44, "name": "spoon" }, + { "id": 45, "name": "bowl" }, + { "id": 46, "name": "banana" }, + { "id": 47, "name": "apple" }, + { "id": 48, "name": "sandwich" }, + { "id": 49, "name": "orange" }, + { "id": 50, "name": "broccoli" }, + { "id": 51, "name": "carrot" }, + { "id": 52, "name": "hot dog" }, + { "id": 53, "name": "pizza" }, + { "id": 54, "name": "donut" }, + { "id": 55, "name": "cake" }, + { "id": 56, "name": "chair" }, + { "id": 57, "name": "sofa" }, + { "id": 58, "name": "pottedplant" }, + { "id": 59, "name": "bed" }, + { "id": 60, "name": "diningtable" }, + { "id": 61, "name": "toilet" }, + { "id": 62, "name": "tvmonitor" }, + { "id": 63, "name": "laptop" }, + { "id": 64, "name": "mouse" }, + { "id": 65, "name": "remote" }, + { "id": 66, "name": "keyboard" }, + { "id": 67, "name": "cell phone" }, + { "id": 68, "name": "microwave" }, + { "id": 69, "name": "oven" }, + { "id": 70, "name": "toaster" }, + { "id": 71, "name": "sink" }, + { "id": 72, "name": "refrigerator" }, + { "id": 73, "name": "book" }, + { "id": 74, "name": "clock" }, + { "id": 75, "name": "vase" }, + { "id": 76, "name": "scissors" }, + { "id": 77, "name": "teddy bear" }, + { "id": 78, "name": "hair drier" }, + { "id": 79, "name": "toothbrush" } + ] spec: description: YOLO v3 from Intel Open Model Zoo @@ -46,4 +129,4 @@ spec: restartPolicy: name: always - maximumRetryCount: 3 \ No newline at end of file + maximumRetryCount: 3 From aea4665449c740497507cc8627dbd05634d57af4 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Wed, 17 Jun 2020 13:40:23 +0300 Subject: [PATCH 26/98] Added health check for containers (nuclio dashboard feature) --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 02d0235a1836..3054acb001da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -108,6 +108,8 @@ services: volumes: - /tmp:/tmp - /var/run/docker.sock:/var/run/docker.sock + environment: + NUCLIO_CHECK_FUNCTION_CONTAINERS_HEALTHINESS: "true" ports: - "8070:8070" From 9490d1ab4c6c3abc3a754611d282b1e3a8587596 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Thu, 18 Jun 2020 15:50:30 +0300 Subject: [PATCH 27/98] Read labels spec from function.yaml. --- datumaro/.gitignore | 1 + .../nuclio/main.py | 86 +----------------- .../nuclio/main.py | 86 +----------------- .../public/yolov-v3-tf/nuclio/main.py | 87 +------------------ 4 files changed, 13 insertions(+), 247 deletions(-) create mode 100644 datumaro/.gitignore diff --git a/datumaro/.gitignore b/datumaro/.gitignore new file mode 100644 index 000000000000..17c3ea8bcfbf --- /dev/null +++ b/datumaro/.gitignore @@ -0,0 +1 @@ +/datumaro.egg-info diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py index 341bfab60c97..ed46408332ff 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/main.py @@ -4,6 +4,7 @@ import io from model_loader import ModelLoader import numpy as np +import yaml def init_context(context): context.logger.info("Init context... 0%") @@ -11,88 +12,9 @@ def init_context(context): model_bin = "/opt/nuclio/open_model_zoo/public/faster_rcnn_inception_v2_coco/FP32/faster_rcnn_inception_v2_coco.bin" model_handler = ModelLoader(model_xml, model_bin) setattr(context.user_data, 'model_handler', model_handler) - labels = { - 1: "person", - 2: "bicycle", - 3: "car", - 4: "motorcycle", - 5: "airplane", - 6: "bus", - 7: "train", - 8: "truck", - 9: "boat", - 10: "traffic_light", - 11: "fire_hydrant", - 13: "stop_sign", - 14: "parking_meter", - 15: "bench", - 16: "bird", - 17: "cat", - 18: "dog", - 19: "horse", - 20: "sheep", - 21: "cow", - 22: "elephant", - 23: "bear", - 24: "zebra", - 25: "giraffe", - 27: "backpack", - 28: "umbrella", - 31: "handbag", - 32: "tie", - 33: "suitcase", - 34: "frisbee", - 35: "skis", - 36: "snowboard", - 37: "sports_ball", - 38: "kite", - 39: "baseball_bat", - 40: "baseball_glove", - 41: "skateboard", - 42: "surfboard", - 43: "tennis_racket", - 44: "bottle", - 46: "wine_glass", - 47: "cup", - 48: "fork", - 49: "knife", - 50: "spoon", - 51: "bowl", - 52: "banana", - 53: "apple", - 54: "sandwich", - 55: "orange", - 56: "broccoli", - 57: "carrot", - 58: "hot_dog", - 59: "pizza", - 60: "donut", - 61: "cake", - 62: "chair", - 63: "couch", - 64: "potted_plant", - 65: "bed", - 67: "dining_table", - 70: "toilet", - 72: "tv", - 73: "laptop", - 74: "mouse", - 75: "remote", - 76: "keyboard", - 77: "cell_phone", - 78: "microwave", - 79: "oven", - 80: "toaster", - 81: "sink", - 83: "refrigerator", - 84: "book", - 85: "clock", - 86: "vase", - 87: "scissors", - 88: "teddy_bear", - 89: "hair_drier", - 90: "toothbrush" - } + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} setattr(context.user_data, "labels", labels) context.logger.info("Init context...100%") diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py index 2773dd621316..6fc0b3da2a6c 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/main.py @@ -6,6 +6,7 @@ import numpy as np import cv2 from skimage.measure import approximate_polygon, find_contours +import yaml def init_context(context): context.logger.info("Init context... 0%") @@ -13,88 +14,9 @@ def init_context(context): model_bin = "/opt/nuclio/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/FP32/mask_rcnn_inception_resnet_v2_atrous_coco.bin" model_handler = ModelLoader(model_xml, model_bin) setattr(context.user_data, 'model_handler', model_handler) - labels = { - 1: "person", - 2: "bicycle", - 3: "car", - 4: "motorcycle", - 5: "airplane", - 6: "bus", - 7: "train", - 8: "truck", - 9: "boat", - 10: "traffic_light", - 11: "fire_hydrant", - 13: "stop_sign", - 14: "parking_meter", - 15: "bench", - 16: "bird", - 17: "cat", - 18: "dog", - 19: "horse", - 20: "sheep", - 21: "cow", - 22: "elephant", - 23: "bear", - 24: "zebra", - 25: "giraffe", - 27: "backpack", - 28: "umbrella", - 31: "handbag", - 32: "tie", - 33: "suitcase", - 34: "frisbee", - 35: "skis", - 36: "snowboard", - 37: "sports_ball", - 38: "kite", - 39: "baseball_bat", - 40: "baseball_glove", - 41: "skateboard", - 42: "surfboard", - 43: "tennis_racket", - 44: "bottle", - 46: "wine_glass", - 47: "cup", - 48: "fork", - 49: "knife", - 50: "spoon", - 51: "bowl", - 52: "banana", - 53: "apple", - 54: "sandwich", - 55: "orange", - 56: "broccoli", - 57: "carrot", - 58: "hot_dog", - 59: "pizza", - 60: "donut", - 61: "cake", - 62: "chair", - 63: "couch", - 64: "potted_plant", - 65: "bed", - 67: "dining_table", - 70: "toilet", - 72: "tv", - 73: "laptop", - 74: "mouse", - 75: "remote", - 76: "keyboard", - 77: "cell_phone", - 78: "microwave", - 79: "oven", - 80: "toaster", - 81: "sink", - 83: "refrigerator", - 84: "book", - 85: "clock", - 86: "vase", - 87: "scissors", - 88: "teddy_bear", - 89: "hair_drier", - 90: "toothbrush" - } + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} setattr(context.user_data, "labels", labels) context.logger.info("Init context...100%") diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py index d355ef99cecb..e88d61ddf26a 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/main.py @@ -5,7 +5,7 @@ from model_loader import ModelLoader import numpy as np from math import exp - +import yaml def init_context(context): context.logger.info("Init context... 0%") @@ -13,88 +13,9 @@ def init_context(context): model_bin = "/opt/nuclio/open_model_zoo/public/yolo-v3-tf/FP32/yolo-v3-tf.bin" model_handler = ModelLoader(model_xml, model_bin) setattr(context.user_data, 'model_handler', model_handler) - labels = { - 0: "person", - 1: "bicycle", - 2: "car", - 3: "motorbike", - 4: "aeroplane", - 5: "bus", - 6: "train", - 7: "truck", - 8: "boat", - 9: "traffic light", - 10: "fire hydrant", - 11: "stop sign", - 12: "parking meter", - 13: "bench", - 14: "bird", - 15: "cat", - 16: "dog", - 17: "horse", - 18: "sheep", - 19: "cow", - 20: "elephant", - 21: "bear", - 22: "zebra", - 23: "giraffe", - 24: "backpack", - 25: "umbrella", - 26: "handbag", - 27: "tie", - 28: "suitcase", - 29: "frisbee", - 30: "skis", - 31: "snowboard", - 32: "sports ball", - 33: "kite", - 34: "baseball bat", - 35: "baseball glove", - 36: "skateboard", - 37: "surfboard", - 38: "tennis racket", - 39: "bottle", - 40: "wine glass", - 41: "cup", - 42: "fork", - 43: "knife", - 44: "spoon", - 45: "bowl", - 46: "banana", - 47: "apple", - 48: "sandwich", - 49: "orange", - 50: "broccoli", - 51: "carrot", - 52: "hot dog", - 53: "pizza", - 54: "donut", - 55: "cake", - 56: "chair", - 57: "sofa", - 58: "pottedplant", - 59: "bed", - 60: "diningtable", - 61: "toilet", - 62: "tvmonitor", - 63: "laptop", - 64: "mouse", - 65: "remote", - 66: "keyboard", - 67: "cell phone", - 68: "microwave", - 69: "oven", - 70: "toaster", - 71: "sink", - 72: "refrigerator", - 73: "book", - 74: "clock", - 75: "vase", - 76: "scissors", - 77: "teddy bear", - 78: "hair drier", - 79: "toothbrush" - } + functionconfig = yaml.safe_load(open("/opt/nuclio/function.yaml")) + labels_spec = functionconfig['metadata']['annotations']['spec'] + labels = {item['id']: item['name'] for item in json.loads(labels_spec)} setattr(context.user_data, "labels", labels) context.logger.info("Init context...100%") From ed19ccf35961639c136746e124075a1262cfe766 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Thu, 18 Jun 2020 16:19:07 +0300 Subject: [PATCH 28/98] Added settings for NUCLIO --- cvat/settings/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 453ebd53803b..9c4361b49372 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -252,6 +252,13 @@ def generate_ssh_keys(): } } +NUCLIO = { + 'SCHEME': 'http', + 'HOST': 'localhost', + 'PORT': 8070, + 'DEFAULT_TIMEOUT': 60 +} + RQ_SHOW_ADMIN_LINK = True RQ_EXCEPTION_HANDLERS = ['cvat.apps.engine.views.rq_handler'] From b103703282e8294786013a6214be72d4a5413d58 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Fri, 19 Jun 2020 01:28:22 +0300 Subject: [PATCH 29/98] Fixed a couple of typos. Now it works in most cases. --- cvat/apps/lambda_manager/views.py | 419 ++++++++++++++++++------------ cvat/requirements/base.txt | 4 +- 2 files changed, 254 insertions(+), 169 deletions(-) diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 029124d3bdb8..20f1915640b3 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -4,213 +4,298 @@ from cvat.apps.engine.frame_provider import FrameProvider from cvat.apps.engine.models import Task as TaskModel from cvat.apps.dataset_manager.task import put_task_data, patch_task_data +from django.core.exceptions import ValidationError import base64 import json import requests import django_rq import rq +from functools import wraps from cvat.apps.engine.serializers import LabeledDataSerializer +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist + +class LambdaGateway: + NUCLIO_ROOT_URL = '/api/functions' + + def _http(self, method="get", scheme=None, host=None, port=None, + url=None, data=None): + NUCLIO_GATEWAY = '{}://{}:{}'.format( + scheme or settings.NUCLIO['SCHEME'], + host or settings.NUCLIO['HOST'], + port or settings.NUCLIO['PORT']) + NUCLIO_HEADERS = {'x-nuclio-project-name': 'cvat'} + NUCLIO_TIMEOUT = settings.NUCLIO['DEFAULT_TIMEOUT'] + + if url: + url = "{}{}".format(NUCLIO_GATEWAY, url) + else: + url = NUCLIO_GATEWAY -# FIXME: need to define the host in settings -NUCLIO_GATEWAY = 'http://localhost:8070/api/functions' -NUCLIO_HEADERS = {'x-nuclio-project-name': 'cvat'} -NUCLIO_TIMEOUT = 60 - -class FunctionViewSet(viewsets.ViewSet): - lookup_value_regex = '[a-zA-Z0-9_.-]+' - lookup_field = 'name' - - def list(self, request): - response = [] - try: - reply = requests.get(NUCLIO_GATEWAY, headers=NUCLIO_HEADERS, - timeout=NUCLIO_TIMEOUT) - reply.raise_for_status() - output = reply.json() - for name in output: - response.append(self._extract_function_info(output[name])) - except requests.RequestException as err: - return Response(str(err), status=reply.status_code) - - return Response(data=response) - - @staticmethod - def _get_function(name): - reply = requests.get(NUCLIO_GATEWAY + '/' + name, - headers=NUCLIO_HEADERS, timeout=NUCLIO_TIMEOUT) + reply = getattr(requests, method)(url, headers=NUCLIO_HEADERS, + timeout=NUCLIO_TIMEOUT, json=data) reply.raise_for_status() - output = reply.json() - - return output - - - def retrieve(self, request, name): - try: - output = self._get_function(name) - response = self._extract_function_info(output) - except requests.RequestException as err: - return Response(str(err), status=output.status_code) - - return Response(data=response) - - @staticmethod - def _call(function, task_id, frame, points=None): - reply = FunctionViewSet._get_function(function) - port = reply["status"]["httpPort"] + response = reply.json() + + return response + + def list(self): + data = self._http(url=self.NUCLIO_ROOT_URL) + response = [LambdaFunction(self, item) for item in data.values()] + return response + + def get(self, name): + data = self._http(url=self.NUCLIO_ROOT_URL + '/' + name) + response = LambdaFunction(self, data) + return response + + def invoke(self, func, payload): + return self._http(method="post", port=func.port, data=payload) + +class LambdaFunction: + def __init__(self, gateway, data): + # name of the function (e.g. omz.public.yolo-v3) + self.name = data['metadata']['name'] + # type of the function (e.g. detector, interactor) + self.kind = data['metadata']['annotations'].get('type') + # dictionary of labels for the function (e.g. car, person) + spec = json.loads(data['metadata']['annotations'].get('spec') or '[]') + labels = [item['name'] for item in spec] + if len(labels) != len(set(labels)): + raise ValidationError( + "`{}` lambda function has non-unique labels".format(self.name), + code=status.HTTP_404_NOT_FOUND) + self.labels = labels + # state of the function + self.state = data['status']['state'] + # description of the function + self.description = data['spec']['description'] + # http port to access the serverless function + self.port = data["status"]["httpPort"] + self.gateway = gateway + + def to_dict(self): + response = { + 'name': self.name, + 'kind': self.kind, + 'labels': self.labels, + 'state': self.state, + 'description': self.description + } + return response - data = { - 'image': FunctionViewSet._get_image(task_id, frame), + def invoke(self, db_task, frame, points=None): + payload = { + 'image': self._get_image(db_task, frame), 'points': points } - reply = requests.post('http://localhost:{}'.format(port), - json=data, timeout=NUCLIO_TIMEOUT) - reply.raise_for_status() - - # TODO: validate output of a function (detector, tracker, etc...) - return reply - - - def call(self, request, name): - try: - tid = request.data['task'] - frame = request.data['frame'] - points = request.data.get('points') - - reply = FunctionViewSet._call(function=name, task_id=tid, frame=frame, points=points) - except requests.RequestException as err: - # TODO: need to handle requests exception and return correct status - return Response(str(err), status=status.HTTP_400_BAD_REQUEST) - - return Response(data=reply.json()) + return self.gateway.invoke(self, payload) - @staticmethod - def _get_image(tid, frame): - db_task = TaskModel.objects.get(pk=tid) + def _get_image(self, db_task, frame): frame_provider = FrameProvider(db_task.data) # FIXME: now we cannot use the original quality because nuclio has body # limit size by default 4Mb (from FastHTTP). image = frame_provider.get_frame(frame, quality=FrameProvider.Quality.COMPRESSED) - return base64.b64encode(image[0].getvalue()).decode('utf-8') - @staticmethod - def _extract_function_info(data): + +class LambdaQueue: + def _get_queue(self): + QUEUE_NAME = "low" + return django_rq.get_queue(QUEUE_NAME) + + def get_jobs(self): + queue = self._get_queue() + return [LambdaJob(job) for job in queue.jobs if job.meta.get("lambda")] + + # TODO: protect from running multiple times for the same task + # Only one job for an annotation task + def enqueue(self, lambda_func, threshold, task): + queue = self._get_queue() + # LambdaJob(None) is a workaround for python-rq. It has multiple issues + # with invocation of non trivial functions. For example, it cannot run + # staticmethod, it cannot run a callable class. Thus I provide an object + # which has __call__ function. + job = queue.create_job(LambdaJob(None), + meta = { "lambda": True }, + kwargs = { + "function": lambda_func, + "threshold": threshold, + "task": task + }) + + queue.enqueue_job(job) + + return LambdaJob(job) + + def fetch_job(self, pk): + queue = self._get_queue() + job = queue.fetch_job(pk) + if job == None or not job.meta.get("lambda"): + raise ValidationError("{} lambda job is not found".format(pk), + code=status.HTTP_404_NOT_FOUND) + + return LambdaJob(job) + + +class LambdaJob: + def __init__(self, job): + self.job = job + + def to_dict(self): + lambda_func = self.job.kwargs.get("function") return { - 'name': data['metadata']['name'], - 'kind': data['metadata']['labels'].get('type'), - 'state': data['status']['state'], - 'description': data['spec']['description'] + "id": self.job.id, + "function": { + "name": lambda_func.name if lambda_func else None, + "threshold": self.job.kwargs.get("threshold"), + "task": self.job.kwargs.get("task") + }, + "status": self.job.get_status(), + "enqueued": self.job.enqueued_at, + "started": self.job.started_at, + "ended": self.job.ended_at, + "exc_info": self.job.exc_info } -def _callme(function, threshold, task_id): - try: + def delete(self): + self.job.delete() + + @staticmethod + def __call__(function, threshold, task): # TODO: need to remove annotations if clear flag is True # TODO: need logging - db_task = TaskModel.objects.get(pk=task_id) + db_task = TaskModel.objects.get(pk=task) # TODO: check tasks with a frame step for frame in range(db_task.data.size): - reply = FunctionViewSet._call(function, task_id, frame) - RequestViewSet._save_annotations(db_task, frame, reply.json()) - except requests.RequestException as err: - # TODO: handle exceptions (e.g. no task, etc) - return Response(str(err), status=status.HTTP_400_BAD_REQUEST) + annotations = function.invoke(db_task, frame) + # TODO: optimize + # TODO: need user mapping between model labels and task labels + db_labels = db_task.label_set.prefetch_related("attributespec_set").all() + attributes = {db_label.id: + {db_attr.name: db_attr.id for db_attr in db_label.attributespec_set.all()} + for db_label in db_labels} + labels = {db_label.name:db_label.id for db_label in db_labels} + + # TODO: need to check 'cancel' operation + job = rq.get_current_job() + job.meta["progress"] = frame / db_task.data.size + job.save_meta() + + + # TODO: need to make "tags" and "tracks" are optional + # FIXME: need to provide the correct version here + results = {"version": 0, "tags": [], "shapes": [], "tracks": []} + for anno in annotations: + label_id = labels.get(anno["label"]) + if label_id is not None: + results["shapes"].append({ + "frame": frame, + "label_id": label_id, + "type": anno["type"], + "occluded": False, + "points": anno["points"], + "z_order": 0, + "group": None, + "attributes": [] + }) + + serializer = LabeledDataSerializer(data=results) + # TODO: handle exceptions + if serializer.is_valid(raise_exception=True): + patch_task_data(db_task.id, results, "create") + + +def return_response(success_code=status.HTTP_200_OK): + def wrap_response(func): + @wraps(func) + def func_wrapper(*args, **kwargs): + data = None + status_code = success_code + try: + data = func(*args, **kwargs) + except requests.ConnectionError as err: + status_code = status.HTTP_503_SERVICE_UNAVAILABLE + data = str(err) + except requests.HTTPError as err: + status_code = err.response.status_code + data = str(err) + except requests.Timeout as err: + status_code = status.HTTP_504_GATEWAY_TIMEOUT + data = str(err) + except requests.RequestException as err: + status_code = status.HTTP_500_INTERNAL_SERVER_ERROR + data = str(err) + except ValidationError as err: + status_code = err.code + data = err.message + + return Response(data=data, status=status_code) + + return func_wrapper + return wrap_response +class FunctionViewSet(viewsets.ViewSet): + lookup_value_regex = '[a-zA-Z0-9_.-]+' + lookup_field = 'name' -class RequestViewSet(viewsets.ViewSet): - QUEUE_NAME = 'low' + @return_response() + def list(self, request): + gateway = LambdaGateway() + return [f.to_dict() for f in gateway.list()] - @staticmethod - def _get_job(job): - return { - "id": job.id, - "function": { - "name": job.args[0], - "args": job.args[1:] - }, - "status": job.get_status(), - "enqueued": job.enqueued_at, - "started": job.started_at, - "ended": job.ended_at, - "exc_info": job.exc_info - } + @return_response() + def retrieve(self, request, name): + gateway = LambdaGateway() + return gateway.get(name).to_dict() - def list(self, request): - queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) - results = [] - for job in queue.jobs: - results.append(self._get_job(job)) + @return_response() + def call(self, request, name): + try: + task = request.data['task'] + points = request.data.get('points') + frame = request.data['frame'] + db_task = TaskModel.objects.get(pk=task) + except (KeyError, ObjectDoesNotExist) as err: + raise ValidationError( + '`{}` lambda function was run '.format(name) + + 'with wrong arguments ({})'.format(str(err)), + code=status.HTTP_400_BAD_REQUEST) - return Response(data=results) + gateway = LambdaGateway() + lambda_func = gateway.get(name) - @staticmethod - def _save_annotations(db_task, frame, annotations): - # TODO: optimize - # TODO: need user mapping between model labels and task labels - db_labels = db_task.label_set.prefetch_related("attributespec_set").all() - #attributes = {db_label.id: - # {db_attr.name: db_attr.id for db_attr in db_label.attributespec_set.all()} - # for db_label in db_labels} - labels = {db_label.name:db_label.id for db_label in db_labels} - - # TODO: need to check 'cancel' operation - job = rq.get_current_job() - job.meta["progress"] = frame / db_task.data.size - job.save_meta() - - - # TODO: need to make "tags" and "tracks" are optional - # FIXME: need to provide the correct version here - results = {"version": 0, "tags": [], "shapes": [], "tracks": []} - for anno in annotations: - label_id = labels.get(anno["label"]) - if label_id is not None: - results["shapes"].append({ - "frame": frame, - "label_id": label_id, - "type": anno["type"], - "occluded": False, - "points": anno["points"], - "z_order": 0, - "group": None, - "attributes": [] - }) - - serializer = LabeledDataSerializer(data=results) - # TODO: handle exceptions - if serializer.is_valid(raise_exception=True): - patch_task_data(db_task.id, results, "create") - - # { 'function': 'name', 'threshold': 't', 'task': 'id'} + return lambda_func.invoke(db_task, frame, points) + +class RequestViewSet(viewsets.ViewSet): + @return_response() + def list(self, request): + queue = LambdaQueue() + return [job.to_dict() for job in queue.get_jobs()] + + @return_response() def create(self, request): function = request.data['function'] threshold = request.data.get('threshold') - task_id = request.data['task'] - - queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) - # TODO: protect from running multiple times for the same task - # Only one job for an annotation task - job = queue.enqueue(_callme, function, threshold, task_id) - - + task = request.data['task'] - return Response(data={"id": job.id}) + gateway = LambdaGateway() + queue = LambdaQueue() + lambda_func = gateway.get(function) + job = queue.enqueue(lambda_func, threshold, task) + return job.to_dict() + @return_response() def retrieve(self, request, pk): - queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) + queue = LambdaQueue() job = queue.fetch_job(pk) - if job != None: - return Response(data=self._get_job(job)) - else: - return Response(status.HTTP_404_NOT_FOUND) + return job.to_dict() + + @return_response(status.HTTP_204_NO_CONTENT) def delete(self, request, pk): - queue = django_rq.get_queue(RequestViewSet.QUEUE_NAME) + queue = LambdaQueue() job = queue.fetch_job(pk) - if job != None: - job.delete() - return Response(status.HTTP_204_NO_CONTENT) - else: - return Response(status.HTTP_404_NOT_FOUND) - + job.delete() diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index 2460fe1e653d..001f19c183b4 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -4,7 +4,7 @@ django-appconf==1.0.4 django-auth-ldap==2.2.0 django-cacheops==5.0 django-compressor==2.4 -django-rq==2.0.0 +django-rq==2.3.2 EasyProcess==0.2.3 Pillow==7.1.2 numpy==1.18.5 @@ -15,7 +15,7 @@ rcssmin==1.0.6 redis==3.2.0 rjsmin==1.1.0 requests==2.24.0 -rq==1.0.0 +rq==1.4.2 rq-scheduler==0.10.0 scipy==1.4.1 sqlparse==0.2.4 From fc3d6c6c4ee261564af474201aeb4f744873b5d8 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Fri, 19 Jun 2020 22:32:59 +0300 Subject: [PATCH 30/98] Remove Plugin REST API --- .../migrations/0026_auto_20200619_1927.py | 33 +++++++++++++++++++ cvat/apps/engine/models.py | 17 ---------- cvat/apps/engine/serializers.py | 6 ---- cvat/apps/engine/urls.py | 1 - cvat/apps/engine/views.py | 33 ++----------------- 5 files changed, 36 insertions(+), 54 deletions(-) create mode 100644 cvat/apps/engine/migrations/0026_auto_20200619_1927.py diff --git a/cvat/apps/engine/migrations/0026_auto_20200619_1927.py b/cvat/apps/engine/migrations/0026_auto_20200619_1927.py new file mode 100644 index 000000000000..30b1f09f0eb6 --- /dev/null +++ b/cvat/apps/engine/migrations/0026_auto_20200619_1927.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.13 on 2020-06-19 19:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('engine', '0025_auto_20200324_1222'), + ] + + operations = [ + migrations.RemoveField( + model_name='pluginoption', + name='plugin', + ), + migrations.AlterField( + model_name='labeledshape', + name='type', + field=models.CharField(choices=[('rectangle', 'RECTANGLE'), ('polygon', 'POLYGON'), ('polyline', 'POLYLINE'), ('points', 'POINTS'), ('cuboid', 'CUBOID')], max_length=16), + ), + migrations.AlterField( + model_name='trackedshape', + name='type', + field=models.CharField(choices=[('rectangle', 'RECTANGLE'), ('polygon', 'POLYGON'), ('polyline', 'POLYLINE'), ('points', 'POINTS'), ('cuboid', 'CUBOID')], max_length=16), + ), + migrations.DeleteModel( + name='Plugin', + ), + migrations.DeleteModel( + name='PluginOption', + ), + ] diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index c43c12c5440e..cf8905b9e7d1 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -380,20 +380,3 @@ class TrackedShape(Shape): class TrackedShapeAttributeVal(AttributeVal): shape = models.ForeignKey(TrackedShape, on_delete=models.CASCADE) - -class Plugin(models.Model): - name = models.SlugField(max_length=32, primary_key=True) - description = SafeCharField(max_length=8192) - maintainer = models.ForeignKey(User, null=True, blank=True, - on_delete=models.SET_NULL, related_name="maintainers") - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now_add=True) - - # Extend default permission model - class Meta: - default_permissions = () - -class PluginOption(models.Model): - plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE) - name = SafeCharField(max_length=32) - value = SafeCharField(max_length=1024) diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index 8d047e0d8f7d..f1632f565ba1 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -450,12 +450,6 @@ class FileInfoSerializer(serializers.Serializer): name = serializers.CharField(max_length=1024) type = serializers.ChoiceField(choices=["REG", "DIR"]) -class PluginSerializer(serializers.ModelSerializer): - class Meta: - model = models.Plugin - fields = ('name', 'description', 'maintainer', 'created_at', - 'updated_at') - class LogEventSerializer(serializers.Serializer): job_id = serializers.IntegerField(required=False) task_id = serializers.IntegerField(required=False) diff --git a/cvat/apps/engine/urls.py b/cvat/apps/engine/urls.py index 6c608a00f9ed..bb5477530089 100644 --- a/cvat/apps/engine/urls.py +++ b/cvat/apps/engine/urls.py @@ -30,7 +30,6 @@ router.register('jobs', views.JobViewSet) router.register('users', views.UserViewSet) router.register('server', views.ServerViewSet, basename='server') -router.register('plugins', views.PluginViewSet) router.register('restrictions', RestrictionsViewSet, basename='restrictions') urlpatterns = [ diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 120125ea1605..c9d6e5d05eeb 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -37,13 +37,13 @@ from cvat.apps.authentication.decorators import login_required from cvat.apps.dataset_manager.serializers import DatasetFormatsSerializer from cvat.apps.engine.frame_provider import FrameProvider -from cvat.apps.engine.models import Job, Plugin, StatusChoice, Task +from cvat.apps.engine.models import Job, StatusChoice, Task from cvat.apps.engine.serializers import ( AboutSerializer, AnnotationFileSerializer, BasicUserSerializer, DataMetaSerializer, DataSerializer, ExceptionSerializer, FileInfoSerializer, JobSerializer, LabeledDataSerializer, - LogEventSerializer, PluginSerializer, ProjectSerializer, - RqStatusSerializer, TaskSerializer, UserSerializer) + LogEventSerializer, ProjectSerializer, RqStatusSerializer, + TaskSerializer, UserSerializer) from cvat.settings.base import CSS_3RDPARTY, JS_3RDPARTY from cvat.apps.engine.utils import av_scan_paths @@ -757,33 +757,6 @@ def self(self, request): serializer = serializer_class(request.user, context={ "request": request }) return Response(serializer.data) -class PluginViewSet(viewsets.ModelViewSet): - queryset = Plugin.objects.all() - serializer_class = PluginSerializer - - # @action(detail=True, methods=['GET', 'PATCH', 'PUT'], serializer_class=None) - # def config(self, request, name): - # pass - - # @action(detail=True, methods=['GET', 'POST'], serializer_class=None) - # def data(self, request, name): - # pass - - # @action(detail=True, methods=['GET', 'DELETE', 'PATCH', 'PUT'], - # serializer_class=None, url_path='data/(?P\d+)') - # def data_detail(self, request, name, id): - # pass - - - @action(detail=True, methods=['GET', 'POST'], serializer_class=RqStatusSerializer) - def requests(self, request, name): - pass - - @action(detail=True, methods=['GET', 'DELETE'], - serializer_class=RqStatusSerializer, url_path='requests/(?P\d+)') - def request_detail(self, request, name, rq_id): - pass - def rq_handler(job, exc_type, exc_value, tb): job.exc_info = "".join( traceback.format_exception_only(exc_type, exc_value)) From 7a84e4cdaf0080649e0257a1b0ea68b4a9ccf710 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 00:21:16 +0300 Subject: [PATCH 31/98] Remove tf_annotation app (it will be replaced by serverless function) --- Dockerfile | 8 - cvat/apps/tf_annotation/__init__.py | 4 - cvat/apps/tf_annotation/admin.py | 9 - cvat/apps/tf_annotation/apps.py | 11 - .../apps/tf_annotation/migrations/__init__.py | 5 - cvat/apps/tf_annotation/models.py | 9 - cvat/apps/tf_annotation/tests.py | 9 - cvat/apps/tf_annotation/urls.py | 14 - cvat/apps/tf_annotation/views.py | 346 ------------------ cvat/settings/base.py | 3 - cvat/urls.py | 3 - docker-compose.yml | 1 - serverless/dextr/nuclio/function.yaml | 1 + .../nuclio/function.yaml | 1 + .../nuclio/function.yaml | 1 + .../public/yolov-v3-tf/nuclio/function.yaml | 1 + .../nuclio/function.yaml | 130 +++++++ 17 files changed, 134 insertions(+), 422 deletions(-) delete mode 100644 cvat/apps/tf_annotation/__init__.py delete mode 100644 cvat/apps/tf_annotation/admin.py delete mode 100644 cvat/apps/tf_annotation/apps.py delete mode 100644 cvat/apps/tf_annotation/migrations/__init__.py delete mode 100644 cvat/apps/tf_annotation/models.py delete mode 100644 cvat/apps/tf_annotation/tests.py delete mode 100644 cvat/apps/tf_annotation/urls.py delete mode 100644 cvat/apps/tf_annotation/views.py create mode 100644 serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml diff --git a/Dockerfile b/Dockerfile index 1354a0d5d936..8900536beb8a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -88,14 +88,6 @@ RUN if [ "$OPENVINO_TOOLKIT" = "yes" ]; then \ curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.bin -o reid/reid.bin; \ fi -# Tensorflow annotation support -ARG TF_ANNOTATION -ENV TF_ANNOTATION=${TF_ANNOTATION} -ENV TF_ANNOTATION_MODEL_PATH=${HOME}/rcnn/inference_graph -RUN if [ "$TF_ANNOTATION" = "yes" ]; then \ - bash -i /tmp/components/tf_annotation/install.sh; \ - fi - # Auto segmentation support. by Mohammad ARG AUTO_SEGMENTATION ENV AUTO_SEGMENTATION=${AUTO_SEGMENTATION} diff --git a/cvat/apps/tf_annotation/__init__.py b/cvat/apps/tf_annotation/__init__.py deleted file mode 100644 index a0fca4cb39ea..000000000000 --- a/cvat/apps/tf_annotation/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/tf_annotation/admin.py b/cvat/apps/tf_annotation/admin.py deleted file mode 100644 index af8dfc47525b..000000000000 --- a/cvat/apps/tf_annotation/admin.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.contrib import admin - -# Register your models here. - diff --git a/cvat/apps/tf_annotation/apps.py b/cvat/apps/tf_annotation/apps.py deleted file mode 100644 index d07a37b1e932..000000000000 --- a/cvat/apps/tf_annotation/apps.py +++ /dev/null @@ -1,11 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - - -class TFAnnotationConfig(AppConfig): - name = 'tf_annotation' - diff --git a/cvat/apps/tf_annotation/migrations/__init__.py b/cvat/apps/tf_annotation/migrations/__init__.py deleted file mode 100644 index d8e62e54b356..000000000000 --- a/cvat/apps/tf_annotation/migrations/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - diff --git a/cvat/apps/tf_annotation/models.py b/cvat/apps/tf_annotation/models.py deleted file mode 100644 index cdf3b0827bf1..000000000000 --- a/cvat/apps/tf_annotation/models.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.db import models - -# Create your models here. - diff --git a/cvat/apps/tf_annotation/tests.py b/cvat/apps/tf_annotation/tests.py deleted file mode 100644 index 53bc3b7adb85..000000000000 --- a/cvat/apps/tf_annotation/tests.py +++ /dev/null @@ -1,9 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.test import TestCase - -# Create your tests here. - diff --git a/cvat/apps/tf_annotation/urls.py b/cvat/apps/tf_annotation/urls.py deleted file mode 100644 index f84019be9693..000000000000 --- a/cvat/apps/tf_annotation/urls.py +++ /dev/null @@ -1,14 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('create/task/', views.create), - path('check/task/', views.check), - path('cancel/task/', views.cancel), - path('meta/get', views.get_meta_info), -] diff --git a/cvat/apps/tf_annotation/views.py b/cvat/apps/tf_annotation/views.py deleted file mode 100644 index 1c321f14440a..000000000000 --- a/cvat/apps/tf_annotation/views.py +++ /dev/null @@ -1,346 +0,0 @@ - -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest -from rest_framework.decorators import api_view -from rules.contrib.views import permission_required, objectgetter -from cvat.apps.authentication.decorators import login_required -from cvat.apps.dataset_manager.task import put_task_data -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.engine.serializers import LabeledDataSerializer -from cvat.apps.engine.frame_provider import FrameProvider - -import django_rq -import os -import rq - -import tensorflow as tf -import numpy as np - -from PIL import Image -from cvat.apps.engine.log import slogger - - -def load_image_into_numpy(image): - (im_width, im_height) = image.size - return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8) - - -def run_inference_engine_annotation(image_list, labels_mapping, treshold): - from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network - - def _normalize_box(box, w, h, dw, dh): - xmin = min(int(box[0] * dw * w), w) - ymin = min(int(box[1] * dh * h), h) - xmax = min(int(box[2] * dw * w), w) - ymax = min(int(box[3] * dh * h), h) - return xmin, ymin, xmax, ymax - - result = {} - MODEL_PATH = os.environ.get('TF_ANNOTATION_MODEL_PATH') - if MODEL_PATH is None: - raise OSError('Model path env not found in the system.') - - core_or_plugin = make_plugin_or_core() - network = make_network('{}.xml'.format(MODEL_PATH), '{}.bin'.format(MODEL_PATH)) - input_blob_name = next(iter(network.inputs)) - output_blob_name = next(iter(network.outputs)) - if getattr(core_or_plugin, 'load_network', False): - executable_network = core_or_plugin.load_network(network, 'CPU') - else: - executable_network = core_or_plugin.load(network=network) - job = rq.get_current_job() - - del network - - try: - for image_num, im_name in enumerate(image_list): - - job.refresh() - if 'cancel' in job.meta: - del job.meta['cancel'] - job.save() - return None - job.meta['progress'] = image_num * 100 / len(image_list) - job.save_meta() - - image = Image.open(im_name) - width, height = image.size - image.thumbnail((600, 600), Image.ANTIALIAS) - dwidth, dheight = 600 / image.size[0], 600 / image.size[1] - image = image.crop((0, 0, 600, 600)) - image_np = load_image_into_numpy(image) - image_np = np.transpose(image_np, (2, 0, 1)) - prediction = executable_network.infer(inputs={input_blob_name: image_np[np.newaxis, ...]})[output_blob_name][0][0] - for obj in prediction: - obj_class = int(obj[1]) - obj_value = obj[2] - if obj_class and obj_class in labels_mapping and obj_value >= treshold: - label = labels_mapping[obj_class] - if label not in result: - result[label] = [] - xmin, ymin, xmax, ymax = _normalize_box(obj[3:7], width, height, dwidth, dheight) - result[label].append([image_num, xmin, ymin, xmax, ymax]) - finally: - del executable_network - del plugin - - return result - - -def run_tensorflow_annotation(frame_provider, labels_mapping, treshold): - def _normalize_box(box, w, h): - xmin = int(box[1] * w) - ymin = int(box[0] * h) - xmax = int(box[3] * w) - ymax = int(box[2] * h) - return xmin, ymin, xmax, ymax - - result = {} - model_path = os.environ.get('TF_ANNOTATION_MODEL_PATH') - if model_path is None: - raise OSError('Model path env not found in the system.') - job = rq.get_current_job() - - detection_graph = tf.Graph() - with detection_graph.as_default(): - od_graph_def = tf.GraphDef() - with tf.gfile.GFile(model_path + '.pb', 'rb') as fid: - serialized_graph = fid.read() - od_graph_def.ParseFromString(serialized_graph) - tf.import_graph_def(od_graph_def, name='') - - try: - config = tf.ConfigProto() - config.gpu_options.allow_growth=True - sess = tf.Session(graph=detection_graph, config=config) - frames = frame_provider.get_frames(frame_provider.Quality.ORIGINAL) - for image_num, (image, _) in enumerate(frames): - - job.refresh() - if 'cancel' in job.meta: - del job.meta['cancel'] - job.save() - return None - job.meta['progress'] = image_num * 100 / len(frame_provider) - job.save_meta() - - image = Image.open(image) - width, height = image.size - if width > 1920 or height > 1080: - image = image.resize((width // 2, height // 2), Image.ANTIALIAS) - image_np = load_image_into_numpy(image) - image_np_expanded = np.expand_dims(image_np, axis=0) - - image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') - boxes = detection_graph.get_tensor_by_name('detection_boxes:0') - scores = detection_graph.get_tensor_by_name('detection_scores:0') - classes = detection_graph.get_tensor_by_name('detection_classes:0') - num_detections = detection_graph.get_tensor_by_name('num_detections:0') - (boxes, scores, classes, num_detections) = sess.run([boxes, scores, classes, num_detections], feed_dict={image_tensor: image_np_expanded}) - - for i in range(len(classes[0])): - if classes[0][i] in labels_mapping.keys(): - if scores[0][i] >= treshold: - xmin, ymin, xmax, ymax = _normalize_box(boxes[0][i], width, height) - label = labels_mapping[classes[0][i]] - if label not in result: - result[label] = [] - result[label].append([image_num, xmin, ymin, xmax, ymax]) - finally: - sess.close() - del sess - return result - -def convert_to_cvat_format(data): - result = { - "tracks": [], - "shapes": [], - "tags": [], - "version": 0, - } - - for label in data: - boxes = data[label] - for box in boxes: - result['shapes'].append({ - "type": "rectangle", - "label_id": label, - "frame": box[0], - "points": [box[1], box[2], box[3], box[4]], - "z_order": 0, - "group": None, - "occluded": False, - "attributes": [], - }) - - return result - -def create_thread(tid, labels_mapping, user): - try: - TRESHOLD = 0.5 - # Init rq job - job = rq.get_current_job() - job.meta['progress'] = 0 - job.save_meta() - # Get job indexes and segment length - db_task = TaskModel.objects.get(pk=tid) - # Get image list - image_list = FrameProvider(db_task.data) - - # Run auto annotation by tf - result = None - slogger.glob.info("tf annotation with tensorflow framework for task {}".format(tid)) - result = run_tensorflow_annotation(image_list, labels_mapping, TRESHOLD) - - if result is None: - slogger.glob.info('tf annotation for task {} canceled by user'.format(tid)) - return - - # Modify data format and save - result = convert_to_cvat_format(result) - serializer = LabeledDataSerializer(data = result) - if serializer.is_valid(raise_exception=True): - put_task_data(tid, result) - slogger.glob.info('tf annotation for task {} done'.format(tid)) - except Exception as ex: - try: - slogger.task[tid].exception('exception was occured during tf annotation of the task', exc_info=True) - except: - slogger.glob.exception('exception was occured during tf annotation of the task {}'.format(tid), exc_info=True) - raise ex - -@api_view(['POST']) -@login_required -def get_meta_info(request): - try: - queue = django_rq.get_queue('low') - tids = request.data - result = {} - for tid in tids: - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is not None: - result[tid] = { - "active": job.is_queued or job.is_started, - "success": not job.is_failed - } - - return JsonResponse(result) - except Exception as ex: - slogger.glob.exception('exception was occured during tf meta request', exc_info=True) - return HttpResponseBadRequest(str(ex)) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def create(request, tid): - slogger.glob.info('tf annotation create request for task {}'.format(tid)) - try: - db_task = TaskModel.objects.get(pk=tid) - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is not None and (job.is_started or job.is_queued): - raise Exception("The process is already running") - - db_labels = db_task.label_set.prefetch_related('attributespec_set').all() - db_labels = {db_label.id:db_label.name for db_label in db_labels} - - tf_annotation_labels = { - "person": 1, "bicycle": 2, "car": 3, "motorcycle": 4, "airplane": 5, - "bus": 6, "train": 7, "truck": 8, "boat": 9, "traffic_light": 10, - "fire_hydrant": 11, "stop_sign": 13, "parking_meter": 14, "bench": 15, - "bird": 16, "cat": 17, "dog": 18, "horse": 19, "sheep": 20, "cow": 21, - "elephant": 22, "bear": 23, "zebra": 24, "giraffe": 25, "backpack": 27, - "umbrella": 28, "handbag": 31, "tie": 32, "suitcase": 33, "frisbee": 34, - "skis": 35, "snowboard": 36, "sports_ball": 37, "kite": 38, "baseball_bat": 39, - "baseball_glove": 40, "skateboard": 41, "surfboard": 42, "tennis_racket": 43, - "bottle": 44, "wine_glass": 46, "cup": 47, "fork": 48, "knife": 49, "spoon": 50, - "bowl": 51, "banana": 52, "apple": 53, "sandwich": 54, "orange": 55, "broccoli": 56, - "carrot": 57, "hot_dog": 58, "pizza": 59, "donut": 60, "cake": 61, "chair": 62, - "couch": 63, "potted_plant": 64, "bed": 65, "dining_table": 67, "toilet": 70, - "tv": 72, "laptop": 73, "mouse": 74, "remote": 75, "keyboard": 76, "cell_phone": 77, - "microwave": 78, "oven": 79, "toaster": 80, "sink": 81, "refrigerator": 83, - "book": 84, "clock": 85, "vase": 86, "scissors": 87, "teddy_bear": 88, "hair_drier": 89, - "toothbrush": 90 - } - - labels_mapping = {} - for key, labels in db_labels.items(): - if labels in tf_annotation_labels.keys(): - labels_mapping[tf_annotation_labels[labels]] = key - - if not len(labels_mapping.values()): - raise Exception('No labels found for tf annotation') - - # Run tf annotation job - queue.enqueue_call(func=create_thread, - args=(tid, labels_mapping, request.user), - job_id='tf_annotation.create/{}'.format(tid), - timeout=604800) # 7 days - - slogger.task[tid].info('tensorflow annotation job enqueued with labels {}'.format(labels_mapping)) - - except Exception as ex: - try: - slogger.task[tid].exception("exception was occured during tensorflow annotation request", exc_info=True) - except: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() - -@login_required -@permission_required(perm=['engine.task.access'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def check(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is not None and 'cancel' in job.meta: - return JsonResponse({'status': 'finished'}) - data = {} - if job is None: - data['status'] = 'unknown' - elif job.is_queued: - data['status'] = 'queued' - elif job.is_started: - data['status'] = 'started' - data['progress'] = job.meta['progress'] - elif job.is_finished: - data['status'] = 'finished' - job.delete() - else: - data['status'] = 'failed' - data['stderr'] = job.exc_info - job.delete() - - except Exception: - data['status'] = 'unknown' - - return JsonResponse(data) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def cancel(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is None or job.is_finished or job.is_failed: - raise Exception('Task is not being annotated currently') - elif 'cancel' not in job.meta: - job.meta['cancel'] = True - job.save() - - except Exception as ex: - try: - slogger.task[tid].exception("cannot cancel tensorflow annotation for task #{}".format(tid), exc_info=True) - except: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 9c4361b49372..ceca1494957a 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -161,9 +161,6 @@ def generate_ssh_keys(): 'REGISTER_SERIALIZER': 'cvat.apps.restrictions.serializers.RestrictedRegisterSerializer' } -if 'yes' == os.environ.get('TF_ANNOTATION', 'no'): - INSTALLED_APPS += ['cvat.apps.tf_annotation'] - if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no'): INSTALLED_APPS += ['cvat.apps.auto_annotation'] diff --git a/cvat/urls.py b/cvat/urls.py index 11a14d3af78a..42346edf1dfe 100644 --- a/cvat/urls.py +++ b/cvat/urls.py @@ -31,9 +31,6 @@ path('documentation/', include('cvat.apps.documentation.urls')), ] -if apps.is_installed('cvat.apps.tf_annotation'): - urlpatterns.append(path('tensorflow/annotation/', include('cvat.apps.tf_annotation.urls'))) - if apps.is_installed('cvat.apps.git'): urlpatterns.append(path('git/repository/', include('cvat.apps.git.urls'))) diff --git a/docker-compose.yml b/docker-compose.yml index 3ac3fd6283a2..bc42d73d8286 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,7 +44,6 @@ services: https_proxy: no_proxy: socks_proxy: - TF_ANNOTATION: "no" AUTO_SEGMENTATION: "no" USER: "django" DJANGO_CONFIGURATION: "production" diff --git a/serverless/dextr/nuclio/function.yaml b/serverless/dextr/nuclio/function.yaml index 6cd541963ded..79caf7d8e332 100644 --- a/serverless/dextr/nuclio/function.yaml +++ b/serverless/dextr/nuclio/function.yaml @@ -4,6 +4,7 @@ metadata: annotations: type: interactor spec: + framework: openvino spec: description: Deep Extreme Cut diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml index dffb40c36c7d..8d1839da857e 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -3,6 +3,7 @@ metadata: namespace: cvat annotations: type: detector + framework: openvino spec: | [ { "id": 1, "name": "person" }, diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml index b9bc1651f942..c5421a7f5c68 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -6,6 +6,7 @@ metadata: namespace: cvat annotations: type: detector + framework: openvino spec: | [ { "id": 1, "name": "person" }, diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml index cd868a9d4251..1e1c9b869c0c 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml @@ -3,6 +3,7 @@ metadata: namespace: cvat annotations: type: detector + framework: openvino spec: | [ { "id": 0, "name": "person" }, diff --git a/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml new file mode 100644 index 000000000000..a0504e9725e5 --- /dev/null +++ b/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -0,0 +1,130 @@ +metadata: + name: tf.faster_rcnn_inception_v2_coco + namespace: cvat + annotations: + type: detector + framework: tensorflow + spec: | + [ + { "id": 1, "name": "person" }, + { "id": 2, "name": "bicycle" }, + { "id": 3, "name": "car" }, + { "id": 4, "name": "motorcycle" }, + { "id": 5, "name": "airplane" }, + { "id": 6, "name": "bus" }, + { "id": 7, "name": "train" }, + { "id": 8, "name": "truck" }, + { "id": 9, "name": "boat" }, + { "id":10, "name": "traffic_light" }, + { "id":11, "name": "fire_hydrant" }, + { "id":13, "name": "stop_sign" }, + { "id":14, "name": "parking_meter" }, + { "id":15, "name": "bench" }, + { "id":16, "name": "bird" }, + { "id":17, "name": "cat" }, + { "id":18, "name": "dog" }, + { "id":19, "name": "horse" }, + { "id":20, "name": "sheep" }, + { "id":21, "name": "cow" }, + { "id":22, "name": "elephant" }, + { "id":23, "name": "bear" }, + { "id":24, "name": "zebra" }, + { "id":25, "name": "giraffe" }, + { "id":27, "name": "backpack" }, + { "id":28, "name": "umbrella" }, + { "id":31, "name": "handbag" }, + { "id":32, "name": "tie" }, + { "id":33, "name": "suitcase" }, + { "id":34, "name": "frisbee" }, + { "id":35, "name": "skis" }, + { "id":36, "name": "snowboard" }, + { "id":37, "name": "sports_ball" }, + { "id":38, "name": "kite" }, + { "id":39, "name": "baseball_bat" }, + { "id":40, "name": "baseball_glove" }, + { "id":41, "name": "skateboard" }, + { "id":42, "name": "surfboard" }, + { "id":43, "name": "tennis_racket" }, + { "id":44, "name": "bottle" }, + { "id":46, "name": "wine_glass" }, + { "id":47, "name": "cup" }, + { "id":48, "name": "fork" }, + { "id":49, "name": "knife" }, + { "id":50, "name": "spoon" }, + { "id":51, "name": "bowl" }, + { "id":52, "name": "banana" }, + { "id":53, "name": "apple" }, + { "id":54, "name": "sandwich" }, + { "id":55, "name": "orange" }, + { "id":56, "name": "broccoli" }, + { "id":57, "name": "carrot" }, + { "id":58, "name": "hot_dog" }, + { "id":59, "name": "pizza" }, + { "id":60, "name": "donut" }, + { "id":61, "name": "cake" }, + { "id":62, "name": "chair" }, + { "id":63, "name": "couch" }, + { "id":64, "name": "potted_plant" }, + { "id":65, "name": "bed" }, + { "id":67, "name": "dining_table" }, + { "id":70, "name": "toilet" }, + { "id":72, "name": "tv" }, + { "id":73, "name": "laptop" }, + { "id":74, "name": "mouse" }, + { "id":75, "name": "remote" }, + { "id":76, "name": "keyboard" }, + { "id":77, "name": "cell_phone" }, + { "id":78, "name": "microwave" }, + { "id":79, "name": "oven" }, + { "id":80, "name": "toaster" }, + { "id":81, "name": "sink" }, + { "id":83, "name": "refrigerator" }, + { "id":84, "name": "book" }, + { "id":85, "name": "clock" }, + { "id":86, "name": "vase" }, + { "id":87, "name": "scissors" }, + { "id":88, "name": "teddy_bear" }, + { "id":89, "name": "hair_drier" }, + { "id":90, "name": "toothbrush" } + ] + +spec: + description: Faster RCNN from Tensorflow Object Detection API + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + + build: + image: cvat/tf/faster_rcnn_inception_v2_coco + baseImage: tensorflow/tensorflow:2.1.1 + + directives: + preCopy: + - kind: RUN + value: apt install curl + - kind: WORKDIR + value: /opt/nuclio + + postCopy: + - kind: RUN + value: curl http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_2018_01_28.tar.gz -o model.tar.gz + - kind: RUN + value: tar -xzf model.tar.gz && rm model.tar.gz + - kind: RUN + value: ln -s faster_rcnn_inception_v2_coco_2018_01_28 rcnn + - kind: RUN + value: ln -s rcnn/frozen_inference_graph.pb rcnn/inference_graph.pb + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + + platform: + attributes: + network: "cvat_default" + + restartPolicy: + name: always + maximumRetryCount: 3 From 093e4e78f605494121faf4305e8923368f69f312 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 00:33:50 +0300 Subject: [PATCH 32/98] Remove tf_annotation and cuda components --- components/cuda/README.md | 41 --- components/cuda/docker-compose.cuda.yml | 23 -- components/cuda/install.sh | 38 -- components/tf_annotation/README.md | 41 --- .../docker-compose.tf_annotation.yml | 13 - components/tf_annotation/install.sh | 15 - components/tf_annotation/nuclio/function.yaml | 36 -- components/tf_annotation/nuclio/main.py | 0 .../tf_annotation/nuclio/tf_inference.py | 324 ------------------ cvat/apps/documentation/installation.md | 6 - 10 files changed, 537 deletions(-) delete mode 100644 components/cuda/README.md delete mode 100644 components/cuda/docker-compose.cuda.yml delete mode 100755 components/cuda/install.sh delete mode 100644 components/tf_annotation/README.md delete mode 100644 components/tf_annotation/docker-compose.tf_annotation.yml delete mode 100755 components/tf_annotation/install.sh delete mode 100644 components/tf_annotation/nuclio/function.yaml delete mode 100644 components/tf_annotation/nuclio/main.py delete mode 100644 components/tf_annotation/nuclio/tf_inference.py diff --git a/components/cuda/README.md b/components/cuda/README.md deleted file mode 100644 index a6ecbfefba1b..000000000000 --- a/components/cuda/README.md +++ /dev/null @@ -1,41 +0,0 @@ -## [NVIDIA CUDA Toolkit](https://developer.nvidia.com/cuda-toolkit) - -### Requirements - -* NVIDIA GPU with a compute capability [3.0 - 7.2] -* Latest GPU driver - -### Installation - -#### Install the latest driver for your graphics card - -```bash -sudo add-apt-repository ppa:graphics-drivers/ppa -sudo apt-get update -sudo apt-cache search nvidia-* # find latest nvidia driver -sudo apt-get --no-install-recommends install nvidia-* # install the nvidia driver -sudo apt-get --no-install-recommends install mesa-common-dev -sudo apt-get --no-install-recommends install freeglut3-dev -sudo apt-get --no-install-recommends install nvidia-modprobe -``` - -#### Reboot your PC and verify installation by `nvidia-smi` command. - -#### Install [Nvidia-Docker](https://github.com/NVIDIA/nvidia-docker) - -Please be sure that installation was successful. -```bash -docker info | grep 'Runtimes' # output should contains 'nvidia' -``` - -### Build docker image -```bash -# From project root directory -docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml build -``` - -### Run docker container -```bash -# From project root directory -docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml up -d -``` diff --git a/components/cuda/docker-compose.cuda.yml b/components/cuda/docker-compose.cuda.yml deleted file mode 100644 index 41d325f3f2bf..000000000000 --- a/components/cuda/docker-compose.cuda.yml +++ /dev/null @@ -1,23 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -version: "2.3" - -services: - cvat: - build: - context: . - args: - CUDA_SUPPORT: "yes" - runtime: "nvidia" - environment: - NVIDIA_VISIBLE_DEVICES: all - NVIDIA_DRIVER_CAPABILITIES: compute,utility - # That environment variable is used by the Nvidia Container Runtime. - # The Nvidia Container Runtime parses this as: - # :space:: logical OR - # ,: Logical AND - # https://gitlab.com/nvidia/container-images/cuda/issues/31#note_149432780 - NVIDIA_REQUIRE_CUDA: "cuda>=10.0 brand=tesla,driver>=384,driver<385 brand=tesla,driver>=410,driver<411" diff --git a/components/cuda/install.sh b/components/cuda/install.sh deleted file mode 100755 index 58f99acff509..000000000000 --- a/components/cuda/install.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -set -e - -NVIDIA_GPGKEY_SUM=d1be581509378368edeec8c1eb2958702feedf3bc3d17011adbf24efacce4ab5 && \ -NVIDIA_GPGKEY_FPR=ae09fe4bbd223a84b2ccfce3f60f4b3d7fa2af80 && \ -apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub && \ -apt-key adv --export --no-emit-version -a $NVIDIA_GPGKEY_FPR | tail -n +5 > cudasign.pub && \ -echo "$NVIDIA_GPGKEY_SUM cudasign.pub" | sha256sum -c --strict - && rm cudasign.pub && \ -echo "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64 /" > /etc/apt/sources.list.d/cuda.list && \ -echo "deb http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64 /" > /etc/apt/sources.list.d/nvidia-ml.list - -CUDA_VERSION=10.0.130 -NCCL_VERSION=2.5.6 -CUDNN_VERSION=7.6.5.32 -CUDA_PKG_VERSION="10-0=$CUDA_VERSION-1" -echo 'export PATH=/usr/local/nvidia/bin:/usr/local/cuda/bin:${PATH}' >> ${HOME}/.bashrc -echo 'export LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}' >> ${HOME}/.bashrc - -apt-get update && apt-get install -y --no-install-recommends --allow-unauthenticated \ - cuda-cudart-$CUDA_PKG_VERSION \ - cuda-compat-10-0 \ - cuda-libraries-$CUDA_PKG_VERSION \ - cuda-nvtx-$CUDA_PKG_VERSION \ - libnccl2=$NCCL_VERSION-1+cuda10.0 \ - libcudnn7=$CUDNN_VERSION-1+cuda10.0 && \ - ln -s cuda-10.0 /usr/local/cuda && \ - apt-mark hold libnccl2 libcudnn7 && \ - rm -rf /var/lib/apt/lists/* \ - /etc/apt/sources.list.d/nvidia-ml.list /etc/apt/sources.list.d/cuda.list - -python3 -m pip uninstall -y tensorflow -python3 -m pip install --no-cache-dir tensorflow-gpu==1.15.2 - diff --git a/components/tf_annotation/README.md b/components/tf_annotation/README.md deleted file mode 100644 index 5a9a2c10de43..000000000000 --- a/components/tf_annotation/README.md +++ /dev/null @@ -1,41 +0,0 @@ -## [Tensorflow Object Detector](https://github.com/tensorflow/models/tree/master/research/object_detection) - -### What is it? -* This application allows you automatically to annotate many various objects on images. -* It uses [Faster RCNN Inception Resnet v2 Atrous Coco Model](http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz) from [tensorflow detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md) -* It can work on CPU (with Tensorflow or OpenVINO) or GPU (with Tensorflow GPU). -* It supports next classes (just specify them in "labels" row): -``` -'surfboard', 'car', 'skateboard', 'boat', 'clock', -'cat', 'cow', 'knife', 'apple', 'cup', 'tv', -'baseball_bat', 'book', 'suitcase', 'tennis_racket', -'stop_sign', 'couch', 'cell_phone', 'keyboard', -'cake', 'tie', 'frisbee', 'truck', 'fire_hydrant', -'snowboard', 'bed', 'vase', 'teddy_bear', -'toaster', 'wine_glass', 'traffic_light', -'broccoli', 'backpack', 'carrot', 'potted_plant', -'donut', 'umbrella', 'parking_meter', 'bottle', -'sandwich', 'motorcycle', 'bear', 'banana', -'person', 'scissors', 'elephant', 'dining_table', -'toothbrush', 'toilet', 'skis', 'bowl', 'sheep', -'refrigerator', 'oven', 'microwave', 'train', -'orange', 'mouse', 'laptop', 'bench', 'bicycle', -'fork', 'kite', 'zebra', 'baseball_glove', 'bus', -'spoon', 'horse', 'handbag', 'pizza', 'sports_ball', -'airplane', 'hair_drier', 'hot_dog', 'remote', -'sink', 'dog', 'bird', 'giraffe', 'chair'. -``` -* Component adds "Run TF Annotation" button into dashboard. - - -### Build docker image -```bash -# From project root directory -docker-compose -f docker-compose.yml -f components/tf_annotation/docker-compose.tf_annotation.yml build -``` - -### Run docker container -```bash -# From project root directory -docker-compose -f docker-compose.yml -f components/tf_annotation/docker-compose.tf_annotation.yml up -d -``` diff --git a/components/tf_annotation/docker-compose.tf_annotation.yml b/components/tf_annotation/docker-compose.tf_annotation.yml deleted file mode 100644 index 89fd7844f4b5..000000000000 --- a/components/tf_annotation/docker-compose.tf_annotation.yml +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -version: "2.3" - -services: - cvat: - build: - context: . - args: - TF_ANNOTATION: "yes" diff --git a/components/tf_annotation/install.sh b/components/tf_annotation/install.sh deleted file mode 100755 index 8dc034832a2a..000000000000 --- a/components/tf_annotation/install.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT -# -set -e - -cd ${HOME} && \ -curl http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz -o model.tar.gz && \ -tar -xzf model.tar.gz && rm model.tar.gz && \ -mv faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28 ${HOME}/rcnn && cd ${HOME} && \ -mv rcnn/frozen_inference_graph.pb rcnn/inference_graph.pb - -# tensorflow is installed globally diff --git a/components/tf_annotation/nuclio/function.yaml b/components/tf_annotation/nuclio/function.yaml deleted file mode 100644 index a661d7dc6952..000000000000 --- a/components/tf_annotation/nuclio/function.yaml +++ /dev/null @@ -1,36 +0,0 @@ -metadata: - name: tf_faster_rcnn_inception_resnet_v2_atrous_coco - namespace: cvat - -spec: - description: TF Object Detection API (faster RCNN) - runtime: "python:3.6" - handler: main:handler - eventTimeout: 30s - - build: - image: cvat/tf_faster_rcnn_inception_resnet_v2_atrous_coco - baseImage: tensorflow/tensorflow:2.0.1-py3 - - directives: - preCopy: - - kind: RUN - value: apt install curl - - kind: WORKDIR - value: /opt/nuclio - - postCopy: - - kind: RUN - value: curl http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz -o model.tar.gz - - kind: RUN - value: tar -xzf model.tar.gz && rm model.tar.gz - - kind: RUN - value: ln -s faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28 rcnn - - kind: RUN - value: ln -s rcnn/frozen_inference_graph.pb rcnn/inference_graph.pb - - triggers: - myHttpTrigger: - maxWorkers: 2 - kind: "http" - workerAvailabilityTimeoutMilliseconds: 10000 diff --git a/components/tf_annotation/nuclio/main.py b/components/tf_annotation/nuclio/main.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/components/tf_annotation/nuclio/tf_inference.py b/components/tf_annotation/nuclio/tf_inference.py deleted file mode 100644 index 9ca957e2af9e..000000000000 --- a/components/tf_annotation/nuclio/tf_inference.py +++ /dev/null @@ -1,324 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest -from rest_framework.decorators import api_view -from rules.contrib.views import permission_required, objectgetter -from cvat.apps.authentication.decorators import login_required -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.engine.serializers import LabeledDataSerializer -from cvat.apps.engine.annotation import put_task_data -from cvat.apps.engine.frame_provider import FrameProvider - -import os - -import tensorflow as tf -import numpy as np - -from PIL import Image -from cvat.apps.engine.log import slogger - - -def load_image_into_numpy(image): - (im_width, im_height) = image.size - return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8) - - -def run_inference_engine_annotation(image_list, labels_mapping, treshold): - from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network - - def _normalize_box(box, w, h, dw, dh): - xmin = min(int(box[0] * dw * w), w) - ymin = min(int(box[1] * dh * h), h) - xmax = min(int(box[2] * dw * w), w) - ymax = min(int(box[3] * dh * h), h) - return xmin, ymin, xmax, ymax - - result = {} - MODEL_PATH = os.environ.get('TF_ANNOTATION_MODEL_PATH') - if MODEL_PATH is None: - raise OSError('Model path env not found in the system.') - - core_or_plugin = make_plugin_or_core() - network = make_network('{}.xml'.format(MODEL_PATH), '{}.bin'.format(MODEL_PATH)) - input_blob_name = next(iter(network.inputs)) - output_blob_name = next(iter(network.outputs)) - if getattr(core_or_plugin, 'load_network', False): - executable_network = core_or_plugin.load_network(network, 'CPU') - else: - executable_network = core_or_plugin.load(network=network) - job = rq.get_current_job() - - del network - - try: - for image_num, im_name in enumerate(image_list): - - job.refresh() - if 'cancel' in job.meta: - del job.meta['cancel'] - job.save() - return None - job.meta['progress'] = image_num * 100 / len(image_list) - job.save_meta() - - image = Image.open(im_name) - width, height = image.size - image.thumbnail((600, 600), Image.ANTIALIAS) - dwidth, dheight = 600 / image.size[0], 600 / image.size[1] - image = image.crop((0, 0, 600, 600)) - image_np = load_image_into_numpy(image) - image_np = np.transpose(image_np, (2, 0, 1)) - prediction = executable_network.infer(inputs={input_blob_name: image_np[np.newaxis, ...]})[output_blob_name][0][0] - for obj in prediction: - obj_class = int(obj[1]) - obj_value = obj[2] - if obj_class and obj_class in labels_mapping and obj_value >= treshold: - label = labels_mapping[obj_class] - if label not in result: - result[label] = [] - xmin, ymin, xmax, ymax = _normalize_box(obj[3:7], width, height, dwidth, dheight) - result[label].append([image_num, xmin, ymin, xmax, ymax]) - finally: - del executable_network - del plugin - - return result - - -def run_tensorflow_annotation(frame_provider, labels_mapping, treshold): - def _normalize_box(box, w, h): - xmin = int(box[1] * w) - ymin = int(box[0] * h) - xmax = int(box[3] * w) - ymax = int(box[2] * h) - return xmin, ymin, xmax, ymax - - result = {} - model_path = os.environ.get('TF_ANNOTATION_MODEL_PATH') - if model_path is None: - raise OSError('Model path env not found in the system.') - job = rq.get_current_job() - - detection_graph = tf.Graph() - with detection_graph.as_default(): - od_graph_def = tf.GraphDef() - with tf.gfile.GFile(model_path + '.pb', 'rb') as fid: - serialized_graph = fid.read() - od_graph_def.ParseFromString(serialized_graph) - tf.import_graph_def(od_graph_def, name='') - - try: - config = tf.ConfigProto() - config.gpu_options.allow_growth=True - sess = tf.Session(graph=detection_graph, config=config) - frames = frame_provider.get_frames(frame_provider.Quality.ORIGINAL) - for image_num, (image, _) in enumerate(frames): - - job.refresh() - if 'cancel' in job.meta: - del job.meta['cancel'] - job.save() - return None - job.meta['progress'] = image_num * 100 / len(frame_provider) - job.save_meta() - - image = Image.open(image) - width, height = image.size - if width > 1920 or height > 1080: - image = image.resize((width // 2, height // 2), Image.ANTIALIAS) - image_np = load_image_into_numpy(image) - image_np_expanded = np.expand_dims(image_np, axis=0) - - image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') - boxes = detection_graph.get_tensor_by_name('detection_boxes:0') - scores = detection_graph.get_tensor_by_name('detection_scores:0') - classes = detection_graph.get_tensor_by_name('detection_classes:0') - num_detections = detection_graph.get_tensor_by_name('num_detections:0') - (boxes, scores, classes, num_detections) = sess.run([boxes, scores, classes, num_detections], feed_dict={image_tensor: image_np_expanded}) - - for i in range(len(classes[0])): - if classes[0][i] in labels_mapping.keys(): - if scores[0][i] >= treshold: - xmin, ymin, xmax, ymax = _normalize_box(boxes[0][i], width, height) - label = labels_mapping[classes[0][i]] - if label not in result: - result[label] = [] - result[label].append([image_num, xmin, ymin, xmax, ymax]) - finally: - sess.close() - del sess - return result - -def convert_to_cvat_format(data): - result = { - "tracks": [], - "shapes": [], - "tags": [], - "version": 0, - } - - for label in data: - boxes = data[label] - for box in boxes: - result['shapes'].append({ - "type": "rectangle", - "label_id": label, - "frame": box[0], - "points": [box[1], box[2], box[3], box[4]], - "z_order": 0, - "group": None, - "occluded": False, - "attributes": [], - }) - - return result - -def create_thread(tid, labels_mapping, user): - try: - TRESHOLD = 0.5 - # Init rq job - job = rq.get_current_job() - job.meta['progress'] = 0 - job.save_meta() - # Get job indexes and segment length - db_task = TaskModel.objects.get(pk=tid) - # Get image list - image_list = FrameProvider(db_task.data) - - # Run auto annotation by tf - result = None - slogger.glob.info("tf annotation with tensorflow framework for task {}".format(tid)) - result = run_tensorflow_annotation(image_list, labels_mapping, TRESHOLD) - - if result is None: - slogger.glob.info('tf annotation for task {} canceled by user'.format(tid)) - return - - # Modify data format and save - result = convert_to_cvat_format(result) - serializer = LabeledDataSerializer(data = result) - if serializer.is_valid(raise_exception=True): - put_task_data(tid, user, result) - slogger.glob.info('tf annotation for task {} done'.format(tid)) - except Exception as ex: - try: - slogger.task[tid].exception('exception was occured during tf annotation of the task', exc_info=True) - except: - slogger.glob.exception('exception was occured during tf annotation of the task {}'.format(tid), exc_info=True) - raise ex - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def create(request, tid): - slogger.glob.info('tf annotation create request for task {}'.format(tid)) - try: - db_task = TaskModel.objects.get(pk=tid) - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is not None and (job.is_started or job.is_queued): - raise Exception("The process is already running") - - db_labels = db_task.label_set.prefetch_related('attributespec_set').all() - db_labels = {db_label.id:db_label.name for db_label in db_labels} - - tf_annotation_labels = { - "person": 1, "bicycle": 2, "car": 3, "motorcycle": 4, "airplane": 5, - "bus": 6, "train": 7, "truck": 8, "boat": 9, "traffic_light": 10, - "fire_hydrant": 11, "stop_sign": 13, "parking_meter": 14, "bench": 15, - "bird": 16, "cat": 17, "dog": 18, "horse": 19, "sheep": 20, "cow": 21, - "elephant": 22, "bear": 23, "zebra": 24, "giraffe": 25, "backpack": 27, - "umbrella": 28, "handbag": 31, "tie": 32, "suitcase": 33, "frisbee": 34, - "skis": 35, "snowboard": 36, "sports_ball": 37, "kite": 38, "baseball_bat": 39, - "baseball_glove": 40, "skateboard": 41, "surfboard": 42, "tennis_racket": 43, - "bottle": 44, "wine_glass": 46, "cup": 47, "fork": 48, "knife": 49, "spoon": 50, - "bowl": 51, "banana": 52, "apple": 53, "sandwich": 54, "orange": 55, "broccoli": 56, - "carrot": 57, "hot_dog": 58, "pizza": 59, "donut": 60, "cake": 61, "chair": 62, - "couch": 63, "potted_plant": 64, "bed": 65, "dining_table": 67, "toilet": 70, - "tv": 72, "laptop": 73, "mouse": 74, "remote": 75, "keyboard": 76, "cell_phone": 77, - "microwave": 78, "oven": 79, "toaster": 80, "sink": 81, "refrigerator": 83, - "book": 84, "clock": 85, "vase": 86, "scissors": 87, "teddy_bear": 88, "hair_drier": 89, - "toothbrush": 90 - } - - labels_mapping = {} - for key, labels in db_labels.items(): - if labels in tf_annotation_labels.keys(): - labels_mapping[tf_annotation_labels[labels]] = key - - if not len(labels_mapping.values()): - raise Exception('No labels found for tf annotation') - - # Run tf annotation job - queue.enqueue_call(func=create_thread, - args=(tid, labels_mapping, request.user), - job_id='tf_annotation.create/{}'.format(tid), - timeout=604800) # 7 days - - slogger.task[tid].info('tensorflow annotation job enqueued with labels {}'.format(labels_mapping)) - - except Exception as ex: - try: - slogger.task[tid].exception("exception was occured during tensorflow annotation request", exc_info=True) - except: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() - -@login_required -@permission_required(perm=['engine.task.access'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def check(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is not None and 'cancel' in job.meta: - return JsonResponse({'status': 'finished'}) - data = {} - if job is None: - data['status'] = 'unknown' - elif job.is_queued: - data['status'] = 'queued' - elif job.is_started: - data['status'] = 'started' - data['progress'] = job.meta['progress'] - elif job.is_finished: - data['status'] = 'finished' - job.delete() - else: - data['status'] = 'failed' - data['stderr'] = job.exc_info - job.delete() - - except Exception: - data['status'] = 'unknown' - - return JsonResponse(data) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def cancel(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('tf_annotation.create/{}'.format(tid)) - if job is None or job.is_finished or job.is_failed: - raise Exception('Task is not being annotated currently') - elif 'cancel' not in job.meta: - job.meta['cancel'] = True - job.save() - - except Exception as ex: - try: - slogger.task[tid].exception("cannot cancel tensorflow annotation for task #{}".format(tid), exc_info=True) - except: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() diff --git a/cvat/apps/documentation/installation.md b/cvat/apps/documentation/installation.md index 3a2caae000a5..0fb2a2ae5706 100644 --- a/cvat/apps/documentation/installation.md +++ b/cvat/apps/documentation/installation.md @@ -244,16 +244,10 @@ server. Proxy is an advanced topic and it is not covered by the guide. - [Auto annotation using DL models in OpenVINO toolkit format](/cvat/apps/auto_annotation/README.md) - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) -- [TF Object Detection API: auto annotation](/components/tf_annotation/README.md) -- [Support for NVIDIA GPUs](/components/cuda/README.md) - [Semi-automatic segmentation with Deep Extreme Cut](/cvat/apps/dextr_segmentation/README.md) - [Auto segmentation: Keras+Tensorflow Mask R-CNN Segmentation](/components/auto_segmentation/README.md) ```bash -# Build and run containers with CUDA and OpenVINO support -# IMPORTANT: need to download OpenVINO package before running the command -docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml -f components/openvino/docker-compose.openvino.yml up -d --build - # Build and run containers with Analytics component support: docker-compose -f docker-compose.yml -f components/analytics/docker-compose.analytics.yml up -d --build ``` From 31858e9334de89271e065b31b9cf366496b5f06e Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 00:36:37 +0300 Subject: [PATCH 33/98] Cleanup docs and Dockerfile from CUDA component. --- CONTRIBUTING.md | 12 ------------ Dockerfile | 8 -------- README.md | 1 - 3 files changed, 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17488aea4da3..4e78dddf8171 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,18 +117,6 @@ to changes in ``.env/bin/activate`` file are active. export REID_MODEL_DIR="/path/to/dir" # dir must contain .xml and .bin files ``` -### Tensorflow RCNN -- Download RCNN model, unpack it, and save it somewhere: -```sh -curl http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz -o model.tar.gz && \ -tar -xzf model.tar.gz -``` -- Add next lines to ``.env/bin/activate``: -```sh - export TF_ANNOTATION="yes" - export TF_ANNOTATION_MODEL_PATH="/path/to/the/model/graph" # truncate .pb extension -``` - ### Tensorflow Mask RCNN - Download Mask RCNN model, and save it somewhere: ```sh diff --git a/Dockerfile b/Dockerfile index 8900536beb8a..fee5c493fb39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -103,14 +103,6 @@ RUN python3 -m pip install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGUR # pycocotools package is impossible to install with its dependencies by one pip install command RUN python3 -m pip install --no-cache-dir pycocotools==2.0.0 - -# CUDA support -ARG CUDA_SUPPORT -ENV CUDA_SUPPORT=${CUDA_SUPPORT} -RUN if [ "$CUDA_SUPPORT" = "yes" ]; then \ - /tmp/components/cuda/install.sh; \ - fi - ARG CLAM_AV ENV CLAM_AV=${CLAM_AV} RUN if [ "$CLAM_AV" = "yes" ]; then \ diff --git a/README.md b/README.md index f1c74934d826..d272e933aa89 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,6 @@ are visible to users. Disabled features: - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) -- [Support for NVIDIA GPUs](/components/cuda/README.md) Limitations: - No more than 10 tasks per user From 88fed7198a4370c0a5a16e3ba2dc93313e70bebc Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 00:45:03 +0300 Subject: [PATCH 34/98] Just renamed directories inside serverless --- serverless/{ => public}/dextr/nuclio/dextr.py | 0 serverless/{ => public}/dextr/nuclio/function.yaml | 0 serverless/{ => public}/dextr/nuclio/inference_engine.py | 0 serverless/{ => public}/dextr/nuclio/main.py | 0 serverless/{ => public}/dextr/nuclio/python3 | 0 serverless/{ => public}/dextr/openfaas/Dockerfile | 0 serverless/{ => public}/dextr/openfaas/dextr.py | 0 serverless/{ => public}/dextr/openfaas/dextr.yml | 0 serverless/{ => public}/dextr/openfaas/index.py | 0 serverless/{ => public}/dextr/openfaas/index.sh | 0 serverless/{ => public}/dextr/openfaas/inference_engine.py | 0 serverless/{ => public}/dextr/openfaas/requirements.txt | 0 .../{auto_segmentation => public/mask-rcnn}/nuclio/function.yaml | 0 serverless/{auto_segmentation => public/mask-rcnn}/nuclio/main.py | 0 .../{auto_segmentation => public/mask-rcnn}/nuclio/mask_rcnn.py | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename serverless/{ => public}/dextr/nuclio/dextr.py (100%) rename serverless/{ => public}/dextr/nuclio/function.yaml (100%) rename serverless/{ => public}/dextr/nuclio/inference_engine.py (100%) rename serverless/{ => public}/dextr/nuclio/main.py (100%) rename serverless/{ => public}/dextr/nuclio/python3 (100%) rename serverless/{ => public}/dextr/openfaas/Dockerfile (100%) rename serverless/{ => public}/dextr/openfaas/dextr.py (100%) rename serverless/{ => public}/dextr/openfaas/dextr.yml (100%) rename serverless/{ => public}/dextr/openfaas/index.py (100%) rename serverless/{ => public}/dextr/openfaas/index.sh (100%) rename serverless/{ => public}/dextr/openfaas/inference_engine.py (100%) rename serverless/{ => public}/dextr/openfaas/requirements.txt (100%) rename serverless/{auto_segmentation => public/mask-rcnn}/nuclio/function.yaml (100%) rename serverless/{auto_segmentation => public/mask-rcnn}/nuclio/main.py (100%) rename serverless/{auto_segmentation => public/mask-rcnn}/nuclio/mask_rcnn.py (100%) diff --git a/serverless/dextr/nuclio/dextr.py b/serverless/public/dextr/nuclio/dextr.py similarity index 100% rename from serverless/dextr/nuclio/dextr.py rename to serverless/public/dextr/nuclio/dextr.py diff --git a/serverless/dextr/nuclio/function.yaml b/serverless/public/dextr/nuclio/function.yaml similarity index 100% rename from serverless/dextr/nuclio/function.yaml rename to serverless/public/dextr/nuclio/function.yaml diff --git a/serverless/dextr/nuclio/inference_engine.py b/serverless/public/dextr/nuclio/inference_engine.py similarity index 100% rename from serverless/dextr/nuclio/inference_engine.py rename to serverless/public/dextr/nuclio/inference_engine.py diff --git a/serverless/dextr/nuclio/main.py b/serverless/public/dextr/nuclio/main.py similarity index 100% rename from serverless/dextr/nuclio/main.py rename to serverless/public/dextr/nuclio/main.py diff --git a/serverless/dextr/nuclio/python3 b/serverless/public/dextr/nuclio/python3 similarity index 100% rename from serverless/dextr/nuclio/python3 rename to serverless/public/dextr/nuclio/python3 diff --git a/serverless/dextr/openfaas/Dockerfile b/serverless/public/dextr/openfaas/Dockerfile similarity index 100% rename from serverless/dextr/openfaas/Dockerfile rename to serverless/public/dextr/openfaas/Dockerfile diff --git a/serverless/dextr/openfaas/dextr.py b/serverless/public/dextr/openfaas/dextr.py similarity index 100% rename from serverless/dextr/openfaas/dextr.py rename to serverless/public/dextr/openfaas/dextr.py diff --git a/serverless/dextr/openfaas/dextr.yml b/serverless/public/dextr/openfaas/dextr.yml similarity index 100% rename from serverless/dextr/openfaas/dextr.yml rename to serverless/public/dextr/openfaas/dextr.yml diff --git a/serverless/dextr/openfaas/index.py b/serverless/public/dextr/openfaas/index.py similarity index 100% rename from serverless/dextr/openfaas/index.py rename to serverless/public/dextr/openfaas/index.py diff --git a/serverless/dextr/openfaas/index.sh b/serverless/public/dextr/openfaas/index.sh similarity index 100% rename from serverless/dextr/openfaas/index.sh rename to serverless/public/dextr/openfaas/index.sh diff --git a/serverless/dextr/openfaas/inference_engine.py b/serverless/public/dextr/openfaas/inference_engine.py similarity index 100% rename from serverless/dextr/openfaas/inference_engine.py rename to serverless/public/dextr/openfaas/inference_engine.py diff --git a/serverless/dextr/openfaas/requirements.txt b/serverless/public/dextr/openfaas/requirements.txt similarity index 100% rename from serverless/dextr/openfaas/requirements.txt rename to serverless/public/dextr/openfaas/requirements.txt diff --git a/serverless/auto_segmentation/nuclio/function.yaml b/serverless/public/mask-rcnn/nuclio/function.yaml similarity index 100% rename from serverless/auto_segmentation/nuclio/function.yaml rename to serverless/public/mask-rcnn/nuclio/function.yaml diff --git a/serverless/auto_segmentation/nuclio/main.py b/serverless/public/mask-rcnn/nuclio/main.py similarity index 100% rename from serverless/auto_segmentation/nuclio/main.py rename to serverless/public/mask-rcnn/nuclio/main.py diff --git a/serverless/auto_segmentation/nuclio/mask_rcnn.py b/serverless/public/mask-rcnn/nuclio/mask_rcnn.py similarity index 100% rename from serverless/auto_segmentation/nuclio/mask_rcnn.py rename to serverless/public/mask-rcnn/nuclio/mask_rcnn.py From 38fd4e3d2a7119afcdee45ec64740b99899653dd Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 00:57:30 +0300 Subject: [PATCH 35/98] Remove redundant files and code --- cvat/apps/auto_annotation/README.md | 372 ------------------ cvat/apps/auto_annotation/__init__.py | 6 - cvat/apps/auto_annotation/admin.py | 15 - cvat/apps/auto_annotation/apps.py | 15 - cvat/apps/auto_annotation/image_loader.py | 22 -- cvat/apps/auto_annotation/inference.py | 163 -------- cvat/apps/auto_annotation/inference_engine.py | 52 --- .../migrations/0001_initial.py | 39 -- .../auto_annotation/migrations/__init__.py | 5 - cvat/apps/auto_annotation/model_loader.py | 76 ---- cvat/apps/auto_annotation/model_manager.py | 276 ------------- cvat/apps/auto_annotation/models.py | 56 --- cvat/apps/auto_annotation/permissions.py | 29 -- cvat/apps/auto_annotation/tests.py | 4 - cvat/apps/auto_annotation/urls.py | 19 - cvat/apps/auto_annotation/views.py | 265 ------------- cvat/apps/documentation/installation.md | 3 +- cvat/settings/base.py | 3 - cvat/urls.py | 3 - cvat_proxy/conf.d/cvat.conf.template | 2 +- utils/README.md | 4 +- utils/auto_annotation/README.md | 95 ----- utils/auto_annotation/run_model.py | 263 ------------- .../faster_rcnn_inception_v2_coco/README.md | 6 - .../faster_rcnn_inception_v2_coco/interp.py | 19 - .../mapping.json | 84 ---- .../README.md | 32 -- .../interp.py | 64 --- .../mapping.json | 84 ---- utils/open_model_zoo/yolov3/README.md | 22 -- utils/open_model_zoo/yolov3/interp.py | 160 -------- utils/open_model_zoo/yolov3/mapping.json | 84 ---- 32 files changed, 4 insertions(+), 2338 deletions(-) delete mode 100644 cvat/apps/auto_annotation/README.md delete mode 100644 cvat/apps/auto_annotation/__init__.py delete mode 100644 cvat/apps/auto_annotation/admin.py delete mode 100644 cvat/apps/auto_annotation/apps.py delete mode 100644 cvat/apps/auto_annotation/image_loader.py delete mode 100644 cvat/apps/auto_annotation/inference.py delete mode 100644 cvat/apps/auto_annotation/inference_engine.py delete mode 100644 cvat/apps/auto_annotation/migrations/0001_initial.py delete mode 100644 cvat/apps/auto_annotation/migrations/__init__.py delete mode 100644 cvat/apps/auto_annotation/model_loader.py delete mode 100644 cvat/apps/auto_annotation/model_manager.py delete mode 100644 cvat/apps/auto_annotation/models.py delete mode 100644 cvat/apps/auto_annotation/permissions.py delete mode 100644 cvat/apps/auto_annotation/tests.py delete mode 100644 cvat/apps/auto_annotation/urls.py delete mode 100644 cvat/apps/auto_annotation/views.py delete mode 100644 utils/auto_annotation/README.md delete mode 100644 utils/auto_annotation/run_model.py delete mode 100644 utils/open_model_zoo/faster_rcnn_inception_v2_coco/README.md delete mode 100644 utils/open_model_zoo/faster_rcnn_inception_v2_coco/interp.py delete mode 100644 utils/open_model_zoo/faster_rcnn_inception_v2_coco/mapping.json delete mode 100644 utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md delete mode 100644 utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py delete mode 100644 utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json delete mode 100644 utils/open_model_zoo/yolov3/README.md delete mode 100644 utils/open_model_zoo/yolov3/interp.py delete mode 100644 utils/open_model_zoo/yolov3/mapping.json diff --git a/cvat/apps/auto_annotation/README.md b/cvat/apps/auto_annotation/README.md deleted file mode 100644 index 27fecdf80721..000000000000 --- a/cvat/apps/auto_annotation/README.md +++ /dev/null @@ -1,372 +0,0 @@ -## Auto annotation - -- [Description](#description) -- [Installation](#installation) -- [Usage](#usage) -- [Testing script](#testing) -- [Examples](#examples) - - [Person-vehicle-bike-detection-crossroad-0078](#person-vehicle-bike-detection-crossroad-0078-openvino-toolkit) - - [Landmarks-regression-retail-0009](#landmarks-regression-retail-0009-openvino-toolkit) - - [Semantic Segmentation](#semantic-segmentation) -- [Available interpretation scripts](#available-interpretation-scripts) - -### Description - -The application will be enabled automatically if -[OpenVINO™ component](../../../components/openvino) -is installed. It allows to use custom models for auto annotation. Only models in -OpenVINO™ toolkit format are supported. If you would like to annotate a -task with a custom model please convert it to the intermediate representation -(IR) format via the model optimizer tool. See [OpenVINO documentation](https://software.intel.com/en-us/articles/OpenVINO-InferEngine) for details. - -### Installation - -See the installation instructions for [the OpenVINO component](../../../components/openvino) - -### Usage - -To annotate a task with a custom model you need to prepare 4 files: -1. __Model config__ (*.xml) - a text file with network configuration. -1. __Model weights__ (*.bin) - a binary file with trained weights. -1. __Label map__ (*.json) - a simple json file with `label_map` dictionary like -object with string values for label numbers. - Example: - ```json - { - "label_map": { - "0": "background", - "1": "aeroplane", - "2": "bicycle", - "3": "bird", - "4": "boat", - "5": "bottle", - "6": "bus", - "7": "car", - "8": "cat", - "9": "chair", - "10": "cow", - "11": "diningtable", - "12": "dog", - "13": "horse", - "14": "motorbike", - "15": "person", - "16": "pottedplant", - "17": "sheep", - "18": "sofa", - "19": "train", - "20": "tvmonitor" - } - } - ``` -1. __Interpretation script__ (*.py) - a file used to convert net output layer -to a predefined structure which can be processed by CVAT. This code will be run -inside a restricted python's environment, but it's possible to use some -builtin functions like __str, int, float, max, min, range__. - - Also two variables are available in the scope: - - - __detections__ - a list of dictionaries with detections for each frame: - * __frame_id__ - frame number - * __frame_height__ - frame height - * __frame_width__ - frame width - * __detections__ - output np.ndarray (See [ExecutableNetwork.infer](https://software.intel.com/en-us/articles/OpenVINO-InferEngine#inpage-nav-11-6-3) for details). - - - __results__ - an instance of python class with converted results. - Following methods should be used to add shapes: - ```python - # xtl, ytl, xbr, ybr - expected values are float or int - # label - expected value is int - # frame_number - expected value is int - # attributes - dictionary of attribute_name: attribute_value pairs, for example {"confidence": "0.83"} - add_box(self, xtl, ytl, xbr, ybr, label, frame_number, attributes=None) - - # points - list of (x, y) pairs of float or int, for example [(57.3, 100), (67, 102.7)] - # label - expected value is int - # frame_number - expected value is int - # attributes - dictionary of attribute_name: attribute_value pairs, for example {"confidence": "0.83"} - add_points(self, points, label, frame_number, attributes=None) - add_polygon(self, points, label, frame_number, attributes=None) - add_polyline(self, points, label, frame_number, attributes=None) - ``` - -### Testing script - -CVAT comes prepackaged with a small command line helper script to help develop interpretation scripts. - -It includes a small user interface which allows users to feed in images and see the results using -the user interfaces provided by OpenCV. - -See the script and the documentation in the -[auto_annotation directory](https://github.com/opencv/cvat/tree/develop/utils/auto_annotation). - -When using the Auto Annotation runner, it is often helpful to drop into a REPL prompt to interact with the variables -directly. You can do this using the `interact` method from the `code` module. - -```python -# Import the interact method from the `code` module -from code import interact - - -for frame_results in detections: - frame_height = frame_results["frame_height"] - frame_width = frame_results["frame_width"] - frame_number = frame_results["frame_id"] - # Unsure what other data members are in the `frame_results`? Use the `interact method! - interact(local=locals()) -``` - -```bash -$ python cvat/utils/auto_annotation/run_models.py --py /path/to/myfile.py --json /path/to/mapping.json --xml /path/to/inference.xml --bin /path/to/inference.bin -Python 3.6.6 (default, Sep 26 2018, 15:10:10) -[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.10.44.2)] on darwin -Type "help", "copyright", "credits" or "license" for more information. ->>> dir() -['__builtins__', 'frame_results', 'detections', 'frame_number', 'frame_height', 'interact', 'results', 'frame_width'] ->>> type(frame_results) - ->>> frame_results.keys() -dict_keys(['frame_id', 'frame_height', 'frame_width', 'detections']) -``` - -When using the `interact` method, make sure you are running using the _testing script_, and ensure that you _remove it_ - before submitting to the server! If you don't remove it from the server, the code runners will hang during execution, - and you'll have to restart the server to fix them. - -Another useful development method is visualizing the results using OpenCV. This will be discussed more in the -[Semantic Segmentation](#segmentation) section. - -### Examples - -#### [Person-vehicle-bike-detection-crossroad-0078](https://github.com/opencv/open_model_zoo/blob/2018/intel_models/person-vehicle-bike-detection-crossroad-0078/description/person-vehicle-bike-detection-crossroad-0078.md) (OpenVINO toolkit) - -__Links__ -- [person-vehicle-bike-detection-crossroad-0078.xml](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-vehicle-bike-detection-crossroad-0078/FP32/person-vehicle-bike-detection-crossroad-0078.xml) -- [person-vehicle-bike-detection-crossroad-0078.bin](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-vehicle-bike-detection-crossroad-0078/FP32/person-vehicle-bike-detection-crossroad-0078.bin) - -__Task labels__: person vehicle non-vehicle - -__label_map.json__: -```json -{ -"label_map": { - "1": "person", - "2": "vehicle", - "3": "non-vehicle" - } -} -``` -__Interpretation script for SSD based networks__: -```python -def clip(value): - return max(min(1.0, value), 0.0) - -for frame_results in detections: - frame_height = frame_results["frame_height"] - frame_width = frame_results["frame_width"] - frame_number = frame_results["frame_id"] - - for i in range(frame_results["detections"].shape[2]): - confidence = frame_results["detections"][0, 0, i, 2] - if confidence < 0.5: - continue - - results.add_box( - xtl=clip(frame_results["detections"][0, 0, i, 3]) * frame_width, - ytl=clip(frame_results["detections"][0, 0, i, 4]) * frame_height, - xbr=clip(frame_results["detections"][0, 0, i, 5]) * frame_width, - ybr=clip(frame_results["detections"][0, 0, i, 6]) * frame_height, - label=int(frame_results["detections"][0, 0, i, 1]), - frame_number=frame_number, - attributes={ - "confidence": "{:.2f}".format(confidence), - }, - ) -``` - -#### [Landmarks-regression-retail-0009](https://github.com/opencv/open_model_zoo/blob/2018/intel_models/landmarks-regression-retail-0009/description/landmarks-regression-retail-0009.md) (OpenVINO toolkit) - -__Links__ -- [landmarks-regression-retail-0009.xml](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/landmarks-regression-retail-0009/FP32/landmarks-regression-retail-0009.xml) -- [landmarks-regression-retail-0009.bin](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/landmarks-regression-retail-0009/FP32/landmarks-regression-retail-0009.bin) - -__Task labels__: left_eye right_eye tip_of_nose left_lip_corner right_lip_corner - -__label_map.json__: -```json -{ - "label_map": { - "0": "left_eye", - "1": "right_eye", - "2": "tip_of_nose", - "3": "left_lip_corner", - "4": "right_lip_corner" - } -} -``` -__Interpretation script__: -```python -def clip(value): - return max(min(1.0, value), 0.0) - -for frame_results in detections: - frame_height = frame_results["frame_height"] - frame_width = frame_results["frame_width"] - frame_number = frame_results["frame_id"] - - for i in range(0, frame_results["detections"].shape[1], 2): - x = frame_results["detections"][0, i, 0, 0] - y = frame_results["detections"][0, i + 1, 0, 0] - - results.add_points( - points=[(clip(x) * frame_width, clip(y) * frame_height)], - label=i // 2, # see label map and model output specification, - frame_number=frame_number, - ) -``` - -#### Semantic Segmentation - -__Links__ -- [masck_rcnn_resnet50_atrous_coco][1] (OpenvVINO toolkit) -- [CVAT Implemenation][2] - -__label_map.json__: -```json -{ -"label_map": { - "1": "person", - "2": "bicycle", - "3": "car", - } -} -``` - -Note that the above labels are not all the labels in the model! See [here](https://github.com/opencv/cvat/blob/develop/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json). - -**Interpretation script for a semantic segmentation network**: -```python -import numpy as np -import cv2 -from skimage.measure import approximate_polygon, find_contours - - -for frame_results in detections: - frame_height = frame_results['frame_height'] - frame_width = frame_results['frame_width'] - frame_number = frame_results['frame_id'] - detection = frame_results['detections'] - - # The keys for the below two members will vary based on the model - masks = frame_results['masks'] - boxes = frame_results['reshape_do_2d'] - - for box_index, box in enumerate(boxes): - # Again, these indexes specific to this model - class_label = int(box[1]) - box_class_probability = box[2] - - if box_class_probability > 0.2: - xmin = box[3] * frame_width - ymin = box[4] * frame_height - xmax = box[5] * frame_width - ymax = box[6] * frame_width - - box_width = int(xmax - xmin) - box_height = int(ymin - ymax) - - # use the box index and class label index to find the appropriate mask - # note that we need to convert the class label to a zero indexed array by subtracting `1` - class_mask = masks[box_index][class_label - 1] - - # Class mask is a 33 x 33 matrix - # resize it to the bounding box - resized_mask = cv2.resize(class_mask, dsize(box_height, box_width), interpolation=cv2.INTER_CUBIC) - - # Each pixel is a probability, select every pixel above the probability threshold, 0.5 - # Do this using the boolean `>` method - boolean_mask = (resized_mask > 0.5) - - # Convert the boolean values to uint8 - uint8_mask = boolean_mask.astype(np.uint8) * 255 - - # Change the x and y coordinates into integers - xmin = int(round(xmin)) - ymin = int(round(ymin)) - xmax = xmin + box_width - ymax = ymin + box_height - - # Create an empty blank frame, so that we can get the mask polygon in frame coordinates - mask_frame = np.zeros((frame_height, frame_width), dtype=np.uint8) - - # Put the uint8_mask on the mask frame using the integer coordinates - mask_frame[xmin:xmax, ymin:ymax] = uint8_mask - - mask_probability_threshold = 0.5 - # find the contours - contours = find_contours(mask_frame, mask_probability_threshold) - # every bounding box should only have a single contour - contour = contours[0] - contour = np.flip(contour, axis=1) - - # reduce the precision on the polygon - polygon_mask = approximate_polygon(contour, tolerance=2.5) - polygon_mask = polygon_mask.tolist() - - results.add_polygon(polygon_mask, class_label, frame_number) -``` - -Note that it is sometimes hard to see or understand what is happening in a script. -Use of the computer vision module can help you visualize what is happening. - -```python -import cv2 - - -for frame_results in detections: - frame_height = frame_results['frame_height'] - frame_width = frame_results['frame_width'] - detection = frame_results['detections'] - - masks = frame_results['masks'] - boxes = frame_results['reshape_do_2d'] - - for box_index, box in enumerate(boxes): - class_label = int(box[1]) - box_class_probability = box[2] - - if box_class_probability > 0.2: - xmin = box[3] * frame_width - ymin = box[4] * frame_height - xmax = box[5] * frame_width - ymax = box[6] * frame_width - - box_width = int(xmax - xmin) - box_height = int(ymin - ymax) - - class_mask = masks[box_index][class_label - 1] - # Visualize the class mask! - cv2.imshow('class mask', class_mask) - # wait until user presses keys - cv2.waitKeys() - - boolean_mask = (resized_mask > 0.5) - uint8_mask = boolean_mask.astype(np.uint8) * 255 - - # Visualize the class mask after it's been resized! - cv2.imshow('class mask', uint8_mask) - cv2.waitKeys() -``` - -Note that you should _only_ use the above commands while running the [Auto Annotation Model Runner][3]. -Running on the server will likely require a server restart to fix. -The method `cv2.destroyAllWindows()` or `cv2.destroyWindow('your-name-here')` might be required depending on your - implementation. - -### Available interpretation scripts - -CVAT comes prepackaged with several out of the box interpretation scripts. -See them in the [open model zoo directory](https://github.com/opencv/cvat/tree/develop/utils/open_model_zoo) - -[1]: https://github.com/opencv/open_model_zoo/blob/master/models/public/mask_rcnn_resnet50_atrous_coco/model.yml -[2]: https://github.com/opencv/cvat/tree/develop/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco -[3]: https://github.com/opencv/cvat/tree/develop/utils/auto_annotation diff --git a/cvat/apps/auto_annotation/__init__.py b/cvat/apps/auto_annotation/__init__.py deleted file mode 100644 index c929093f7019..000000000000 --- a/cvat/apps/auto_annotation/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT - -default_app_config = 'cvat.apps.auto_annotation.apps.AutoAnnotationConfig' diff --git a/cvat/apps/auto_annotation/admin.py b/cvat/apps/auto_annotation/admin.py deleted file mode 100644 index da1eabb4cbb3..000000000000 --- a/cvat/apps/auto_annotation/admin.py +++ /dev/null @@ -1,15 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.contrib import admin -from .models import AnnotationModel - -@admin.register(AnnotationModel) -class AnnotationModelAdmin(admin.ModelAdmin): - list_display = ('name', 'owner', 'created_date', 'updated_date', - 'shared', 'primary', 'framework') - - def has_add_permission(self, request): - return False diff --git a/cvat/apps/auto_annotation/apps.py b/cvat/apps/auto_annotation/apps.py deleted file mode 100644 index cea75abfa48c..000000000000 --- a/cvat/apps/auto_annotation/apps.py +++ /dev/null @@ -1,15 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - - -class AutoAnnotationConfig(AppConfig): - name = "cvat.apps.auto_annotation" - - def ready(self): - from .permissions import setup_permissions - - setup_permissions() diff --git a/cvat/apps/auto_annotation/image_loader.py b/cvat/apps/auto_annotation/image_loader.py deleted file mode 100644 index 17335beba13e..000000000000 --- a/cvat/apps/auto_annotation/image_loader.py +++ /dev/null @@ -1,22 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import cv2 -import numpy as np - -class ImageLoader(): - def __init__(self, frame_provider): - self._frame_provider = frame_provider - - def __iter__(self): - for frame, _ in self._frame_provider.get_frames(self._frame_provider.Quality.ORIGINAL): - yield self._load_image(frame) - - def __len__(self): - return len(self._frame_provider) - - @staticmethod - def _load_image(image): - return cv2.imdecode(np.fromstring(image.read(), np.uint8), cv2.IMREAD_COLOR) diff --git a/cvat/apps/auto_annotation/inference.py b/cvat/apps/auto_annotation/inference.py deleted file mode 100644 index b51cc10e8fc8..000000000000 --- a/cvat/apps/auto_annotation/inference.py +++ /dev/null @@ -1,163 +0,0 @@ -import itertools -from .model_loader import ModelLoader -from cvat.apps.engine.utils import import_modules, execute_python_code - -def _process_detections(detections, path_to_conv_script, restricted=True): - results = Results() - local_vars = { - "detections": detections, - "results": results, - } - source_code = open(path_to_conv_script).read() - - if restricted: - global_vars = { - "__builtins__": { - "str": str, - "int": int, - "float": float, - "max": max, - "min": min, - "range": range, - }, - } - else: - global_vars = globals() - imports = import_modules(source_code) - global_vars.update(imports) - - - execute_python_code(source_code, global_vars, local_vars) - - return results - -def _process_attributes(shape_attributes, label_attr_spec): - attributes = [] - for attr_text, attr_value in shape_attributes.items(): - if attr_text in label_attr_spec: - attributes.append({ - "spec_id": label_attr_spec[attr_text], - "value": attr_value, - }) - - return attributes - -class Results(): - def __init__(self): - self._results = { - "shapes": [], - "tracks": [] - } - - # https://stackoverflow.com/a/50928627/2701402 - def add_box(self, xtl: float, ytl: float, xbr: float, ybr: float, label: int, frame_number: int, attributes: dict=None): - """ - xtl - x coordinate, top left - ytl - y coordinate, top left - xbr - x coordinate, bottom right - ybr - y coordinate, bottom right - """ - self.get_shapes().append({ - "label": label, - "frame": frame_number, - "points": [xtl, ytl, xbr, ybr], - "type": "rectangle", - "attributes": attributes or {}, - }) - - def add_points(self, points: list, label: int, frame_number: int, attributes: dict=None): - points = self._create_polyshape(points, label, frame_number, attributes) - points["type"] = "points" - self.get_shapes().append(points) - - def add_polygon(self, points: list, label: int, frame_number: int, attributes: dict=None): - polygon = self._create_polyshape(points, label, frame_number, attributes) - polygon["type"] = "polygon" - self.get_shapes().append(polygon) - - def add_polyline(self, points: list, label: int, frame_number: int, attributes: dict=None): - polyline = self._create_polyshape(points, label, frame_number, attributes) - polyline["type"] = "polyline" - self.get_shapes().append(polyline) - - def get_shapes(self): - return self._results["shapes"] - - def get_tracks(self): - return self._results["tracks"] - - @staticmethod - def _create_polyshape(points: list, label: int, frame_number: int, attributes: dict=None): - return { - "label": label, - "frame": frame_number, - "points": list(itertools.chain.from_iterable(points)), - "attributes": attributes or {}, - } - -class InferenceAnnotationRunner: - def __init__(self, data, model_file, weights_file, labels_mapping, - attribute_spec, convertation_file): - self.data = iter(data) - self.data_len = len(data) - self.model = ModelLoader(model=model_file, weights=weights_file) - self.frame_counter = 0 - self.attribute_spec = attribute_spec - self.convertation_file = convertation_file - self.iteration_size = 128 - self.labels_mapping = labels_mapping - - - def run(self, job=None, update_progress=None, restricted=True): - result = { - "shapes": [], - "tracks": [], - "tags": [], - "version": 0 - } - - detections = [] - for _ in range(self.iteration_size): - try: - frame = next(self.data) - except StopIteration: - break - - orig_rows, orig_cols = frame.shape[:2] - - detections.append({ - "frame_id": self.frame_counter, - "frame_height": orig_rows, - "frame_width": orig_cols, - "detections": self.model.infer(frame), - }) - - self.frame_counter += 1 - if job and update_progress and not update_progress(job, self.frame_counter * 100 / self.data_len): - return None, False - - processed_detections = _process_detections(detections, self.convertation_file, restricted=restricted) - - self._add_shapes(processed_detections.get_shapes(), result["shapes"]) - - more_items = self.frame_counter != self.data_len - - return result, more_items - - def _add_shapes(self, shapes, target_container): - for shape in shapes: - if shape["label"] not in self.labels_mapping: - continue - - db_label = self.labels_mapping[shape["label"]] - label_attr_spec = self.attribute_spec.get(db_label) - target_container.append({ - "label_id": db_label, - "frame": shape["frame"], - "points": shape["points"], - "type": shape["type"], - "z_order": 0, - "group": None, - "occluded": False, - "attributes": _process_attributes(shape["attributes"], label_attr_spec), - }) diff --git a/cvat/apps/auto_annotation/inference_engine.py b/cvat/apps/auto_annotation/inference_engine.py deleted file mode 100644 index 766c0eb0cc77..000000000000 --- a/cvat/apps/auto_annotation/inference_engine.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version - -import subprocess -import os -import platform - -_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) - -def _check_instruction(instruction): - return instruction == str.strip( - subprocess.check_output( - 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True - ).decode('utf-8') - ) - - -def make_plugin_or_core(): - version = get_version() - use_core_openvino = False - try: - major, minor, reference = [int(x) for x in version.split('.')] - if major >= 2 and minor >= 1: - use_core_openvino = True - except Exception: - pass - - if use_core_openvino: - ie = IECore() - return ie - - if _IE_PLUGINS_PATH is None: - raise OSError('Inference engine plugin path env not found in the system.') - - plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH]) - if (_check_instruction('avx2')): - plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so')) - elif (_check_instruction('sse4')): - plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so')) - elif platform.system() == 'Darwin': - plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension.dylib')) - else: - raise Exception('Inference engine requires a support of avx2 or sse4.') - - return plugin - - -def make_network(model, weights): - return IENetwork(model = model, weights = weights) diff --git a/cvat/apps/auto_annotation/migrations/0001_initial.py b/cvat/apps/auto_annotation/migrations/0001_initial.py deleted file mode 100644 index ebd8a6e1a24e..000000000000 --- a/cvat/apps/auto_annotation/migrations/0001_initial.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 2.1.3 on 2019-01-24 14:05 - -import cvat.apps.auto_annotation.models -from django.conf import settings -import django.core.files.storage -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='AnnotationModel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', cvat.apps.auto_annotation.models.SafeCharField(max_length=256)), - ('created_date', models.DateTimeField(auto_now_add=True)), - ('updated_date', models.DateTimeField(auto_now_add=True)), - ('model_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)), - ('weights_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)), - ('labelmap_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)), - ('interpretation_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)), - ('shared', models.BooleanField(default=False)), - ('primary', models.BooleanField(default=False)), - ('framework', models.CharField(default=cvat.apps.auto_annotation.models.FrameworkChoice('openvino'), max_length=32)), - ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'default_permissions': (), - }, - ), - ] diff --git a/cvat/apps/auto_annotation/migrations/__init__.py b/cvat/apps/auto_annotation/migrations/__init__.py deleted file mode 100644 index d8e62e54b356..000000000000 --- a/cvat/apps/auto_annotation/migrations/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - diff --git a/cvat/apps/auto_annotation/model_loader.py b/cvat/apps/auto_annotation/model_loader.py deleted file mode 100644 index e48d5c8e4d1a..000000000000 --- a/cvat/apps/auto_annotation/model_loader.py +++ /dev/null @@ -1,76 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import json -import cv2 -import os -import numpy as np - -from cvat.apps.auto_annotation.inference_engine import make_plugin_or_core, make_network - -class ModelLoader(): - def __init__(self, model, weights): - self._model = model - self._weights = weights - - core_or_plugin = make_plugin_or_core() - network = make_network(self._model, self._weights) - - if getattr(core_or_plugin, 'get_supported_layers', False): - supported_layers = core_or_plugin.get_supported_layers(network) - not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] - if len(not_supported_layers) != 0: - raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}". - format(core_or_plugin.device, ", ".join(not_supported_layers))) - - iter_inputs = iter(network.inputs) - self._input_blob_name = next(iter_inputs) - self._input_info_name = '' - self._output_blob_name = next(iter(network.outputs)) - - self._require_image_info = False - - info_names = ('image_info', 'im_info') - - # NOTE: handeling for the inclusion of `image_info` in OpenVino2019 - if any(s in network.inputs for s in info_names): - self._require_image_info = True - self._input_info_name = set(network.inputs).intersection(info_names) - self._input_info_name = self._input_info_name.pop() - if self._input_blob_name in info_names: - self._input_blob_name = next(iter_inputs) - - if getattr(core_or_plugin, 'load_network', False): - self._net = core_or_plugin.load_network(network, - "CPU", - num_requests=2) - else: - self._net = core_or_plugin.load(network=network, num_requests=2) - input_type = network.inputs[self._input_blob_name] - self._input_layout = input_type if isinstance(input_type, list) else input_type.shape - - def infer(self, image): - _, _, h, w = self._input_layout - in_frame = image if image.shape[:-1] == (h, w) else cv2.resize(image, (w, h)) - in_frame = in_frame.transpose((2, 0, 1)) # Change data layout from HWC to CHW - inputs = {self._input_blob_name: in_frame} - if self._require_image_info: - info = np.zeros([1, 3]) - info[0, 0] = h - info[0, 1] = w - # frame number - info[0, 2] = 1 - inputs[self._input_info_name] = info - - results = self._net.infer(inputs) - if len(results) == 1: - return results[self._output_blob_name].copy() - else: - return results.copy() - - -def load_labelmap(labels_path): - with open(labels_path, "r") as f: - return json.load(f)["label_map"] diff --git a/cvat/apps/auto_annotation/model_manager.py b/cvat/apps/auto_annotation/model_manager.py deleted file mode 100644 index 7bac221a0e69..000000000000 --- a/cvat/apps/auto_annotation/model_manager.py +++ /dev/null @@ -1,276 +0,0 @@ -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import django_rq -import numpy as np -import os -import rq -import shutil -import tempfile - -from django.db import transaction -from django.utils import timezone -from django.conf import settings - -from cvat.apps.engine.log import slogger -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.authentication.auth import has_admin_role -from cvat.apps.engine.serializers import LabeledDataSerializer -from cvat.apps.dataset_manager.task import put_task_data, patch_task_data -from cvat.apps.engine.frame_provider import FrameProvider -from cvat.apps.engine.utils import av_scan_paths - -from .models import AnnotationModel, FrameworkChoice -from .model_loader import load_labelmap -from .image_loader import ImageLoader -from .inference import InferenceAnnotationRunner - - -def _remove_old_file(model_file_field): - if model_file_field and os.path.exists(model_file_field.name): - os.remove(model_file_field.name) - -def _update_dl_model_thread(dl_model_id, name, is_shared, model_file, weights_file, labelmap_file, - interpretation_file, run_tests, is_local_storage, delete_if_test_fails, restricted=True): - def _get_file_content(filename): - return os.path.basename(filename), open(filename, "rb") - - def _delete_source_files(): - for f in [model_file, weights_file, labelmap_file, interpretation_file]: - if f: - os.remove(f) - - def _run_test(model_file, weights_file, labelmap_file, interpretation_file): - test_image = np.ones((1024, 1980, 3), np.uint8) * 255 - try: - dummy_labelmap = {key: key for key in load_labelmap(labelmap_file).keys()} - runner = InferenceAnnotationRunner( - data=[test_image,], - model_file=model_file, - weights_file=weights_file, - labels_mapping=dummy_labelmap, - attribute_spec={}, - convertation_file=interpretation_file) - - runner.run(restricted=restricted) - except Exception as e: - return False, str(e) - - return True, "" - - job = rq.get_current_job() - job.meta["progress"] = "Saving data" - job.save_meta() - - with transaction.atomic(): - dl_model = AnnotationModel.objects.select_for_update().get(pk=dl_model_id) - - test_res = True - message = "" - if run_tests: - job.meta["progress"] = "Test started" - job.save_meta() - - test_res, message = _run_test( - model_file=model_file or dl_model.model_file.name, - weights_file=weights_file or dl_model.weights_file.name, - labelmap_file=labelmap_file or dl_model.labelmap_file.name, - interpretation_file=interpretation_file or dl_model.interpretation_file.name, - ) - - if not test_res: - job.meta["progress"] = "Test failed" - if delete_if_test_fails: - shutil.rmtree(dl_model.get_dirname(), ignore_errors=True) - dl_model.delete() - else: - job.meta["progress"] = "Test passed" - job.save_meta() - - # update DL model - if test_res: - if model_file: - _remove_old_file(dl_model.model_file) - dl_model.model_file.save(*_get_file_content(model_file)) - if weights_file: - _remove_old_file(dl_model.weights_file) - dl_model.weights_file.save(*_get_file_content(weights_file)) - if labelmap_file: - _remove_old_file(dl_model.labelmap_file) - dl_model.labelmap_file.save(*_get_file_content(labelmap_file)) - if interpretation_file: - _remove_old_file(dl_model.interpretation_file) - dl_model.interpretation_file.save(*_get_file_content(interpretation_file)) - - if name: - dl_model.name = name - - if is_shared != None: - dl_model.shared = is_shared - - dl_model.updated_date = timezone.now() - dl_model.save() - - if is_local_storage: - _delete_source_files() - - if not test_res: - raise Exception("Model was not properly created/updated. Test failed: {}".format(message)) - -def create_or_update(dl_model_id, name, model_file, weights_file, labelmap_file, interpretation_file, owner, storage, is_shared): - def get_abs_path(share_path): - if not share_path: - return share_path - share_root = settings.SHARE_ROOT - relpath = os.path.normpath(share_path).lstrip('/') - if '..' in relpath.split(os.path.sep): - raise Exception('Permission denied') - abspath = os.path.abspath(os.path.join(share_root, relpath)) - if os.path.commonprefix([share_root, abspath]) != share_root: - raise Exception('Bad file path on share: ' + abspath) - return abspath - - def save_file_as_tmp(data): - if not data: - return None - fd, filename = tempfile.mkstemp() - with open(filename, 'wb') as tmp_file: - for chunk in data.chunks(): - tmp_file.write(chunk) - os.close(fd) - return filename - - is_create_request = dl_model_id is None - if is_create_request: - dl_model_id = create_empty(owner=owner) - - run_tests = bool(model_file or weights_file or labelmap_file or interpretation_file) - if storage != "local": - model_file = get_abs_path(model_file) - weights_file = get_abs_path(weights_file) - labelmap_file = get_abs_path(labelmap_file) - interpretation_file = get_abs_path(interpretation_file) - else: - model_file = save_file_as_tmp(model_file) - weights_file = save_file_as_tmp(weights_file) - labelmap_file = save_file_as_tmp(labelmap_file) - interpretation_file = save_file_as_tmp(interpretation_file) - - files_to_scan = [] - if model_file: - files_to_scan.append(model_file) - if weights_file: - files_to_scan.append(weights_file) - if labelmap_file: - files_to_scan.append(labelmap_file) - if interpretation_file: - files_to_scan.append(interpretation_file) - av_scan_paths(*files_to_scan) - - if owner: - restricted = not has_admin_role(owner) - else: - restricted = not has_admin_role(AnnotationModel.objects.get(pk=dl_model_id).owner) - - rq_id = "auto_annotation.create.{}".format(dl_model_id) - queue = django_rq.get_queue("default") - queue.enqueue_call( - func=_update_dl_model_thread, - args=( - dl_model_id, - name, - is_shared, - model_file, - weights_file, - labelmap_file, - interpretation_file, - run_tests, - storage == "local", - is_create_request, - restricted - ), - job_id=rq_id - ) - - return rq_id - -@transaction.atomic -def create_empty(owner, framework=FrameworkChoice.OPENVINO): - db_model = AnnotationModel( - owner=owner, - ) - db_model.save() - - model_path = db_model.get_dirname() - if os.path.isdir(model_path): - shutil.rmtree(model_path) - os.mkdir(model_path) - - return db_model.id - -@transaction.atomic -def delete(dl_model_id): - dl_model = AnnotationModel.objects.select_for_update().get(pk=dl_model_id) - if dl_model: - if dl_model.primary: - raise Exception("Can not delete primary model {}".format(dl_model_id)) - - shutil.rmtree(dl_model.get_dirname(), ignore_errors=True) - dl_model.delete() - else: - raise Exception("Requested DL model {} doesn't exist".format(dl_model_id)) - -def run_inference_thread(tid, model_file, weights_file, labels_mapping, attributes, convertation_file, reset, user, restricted=True): - def update_progress(job, progress): - job.refresh() - if "cancel" in job.meta: - del job.meta["cancel"] - job.save() - return False - job.meta["progress"] = progress - job.save_meta() - return True - - try: - job = rq.get_current_job() - job.meta["progress"] = 0 - job.save_meta() - db_task = TaskModel.objects.get(pk=tid) - - result = None - slogger.glob.info("auto annotation with openvino toolkit for task {}".format(tid)) - more_data = True - runner = InferenceAnnotationRunner( - data=ImageLoader(FrameProvider(db_task.data)), - model_file=model_file, - weights_file=weights_file, - labels_mapping=labels_mapping, - attribute_spec=attributes, - convertation_file= convertation_file) - while more_data: - result, more_data = runner.run( - job=job, - update_progress=update_progress, - restricted=restricted) - - if result is None: - slogger.glob.info("auto annotation for task {} canceled by user".format(tid)) - return - - serializer = LabeledDataSerializer(data = result) - if serializer.is_valid(raise_exception=True): - if reset: - put_task_data(tid, result) - else: - patch_task_data(tid, result, "create") - - slogger.glob.info("auto annotation for task {} done".format(tid)) - except Exception as e: - try: - slogger.task[tid].exception("exception was occurred during auto annotation of the task", exc_info=True) - except Exception as ex: - slogger.glob.exception("exception was occurred during auto annotation of the task {}: {}".format(tid, str(ex)), exc_info=True) - raise ex - - raise e diff --git a/cvat/apps/auto_annotation/models.py b/cvat/apps/auto_annotation/models.py deleted file mode 100644 index 467997e0f9b2..000000000000 --- a/cvat/apps/auto_annotation/models.py +++ /dev/null @@ -1,56 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import os -from enum import Enum - -from django.db import models -from django.conf import settings -from django.contrib.auth.models import User -from django.core.files.storage import FileSystemStorage - -fs = FileSystemStorage() - -def upload_path_handler(instance, filename): - return os.path.join(settings.MODELS_ROOT, str(instance.id), filename) - -class FrameworkChoice(Enum): - OPENVINO = 'openvino' - TENSORFLOW = 'tensorflow' - PYTORCH = 'pytorch' - - def __str__(self): - return self.value - - -class SafeCharField(models.CharField): - def get_prep_value(self, value): - value = super().get_prep_value(value) - if value: - return value[:self.max_length] - return value - -class AnnotationModel(models.Model): - name = SafeCharField(max_length=256) - owner = models.ForeignKey(User, null=True, blank=True, - on_delete=models.SET_NULL) - created_date = models.DateTimeField(auto_now_add=True) - updated_date = models.DateTimeField(auto_now_add=True) - model_file = models.FileField(upload_to=upload_path_handler, storage=fs) - weights_file = models.FileField(upload_to=upload_path_handler, storage=fs) - labelmap_file = models.FileField(upload_to=upload_path_handler, storage=fs) - interpretation_file = models.FileField(upload_to=upload_path_handler, storage=fs) - shared = models.BooleanField(default=False) - primary = models.BooleanField(default=False) - framework = models.CharField(max_length=32, default=FrameworkChoice.OPENVINO) - - class Meta: - default_permissions = () - - def get_dirname(self): - return "{models_root}/{id}".format(models_root=settings.MODELS_ROOT, id=self.id) - - def __str__(self): - return self.name diff --git a/cvat/apps/auto_annotation/permissions.py b/cvat/apps/auto_annotation/permissions.py deleted file mode 100644 index ede8d611248a..000000000000 --- a/cvat/apps/auto_annotation/permissions.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import rules - -from cvat.apps.authentication.auth import has_admin_role, has_user_role - -@rules.predicate -def is_model_owner(db_user, db_dl_model): - return db_dl_model.owner == db_user - -@rules.predicate -def is_shared_model(_, db_dl_model): - return db_dl_model.shared - -@rules.predicate -def is_primary_model(_, db_dl_model): - return db_dl_model.primary - -def setup_permissions(): - rules.add_perm('auto_annotation.model.create', has_admin_role | has_user_role) - - rules.add_perm('auto_annotation.model.update', (has_admin_role | is_model_owner) & ~is_primary_model) - - rules.add_perm('auto_annotation.model.delete', (has_admin_role | is_model_owner) & ~is_primary_model) - - rules.add_perm('auto_annotation.model.access', has_admin_role | is_model_owner | - is_shared_model | is_primary_model) diff --git a/cvat/apps/auto_annotation/tests.py b/cvat/apps/auto_annotation/tests.py deleted file mode 100644 index a59acdef3783..000000000000 --- a/cvat/apps/auto_annotation/tests.py +++ /dev/null @@ -1,4 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/auto_annotation/urls.py b/cvat/apps/auto_annotation/urls.py deleted file mode 100644 index 2aa75c5e664a..000000000000 --- a/cvat/apps/auto_annotation/urls.py +++ /dev/null @@ -1,19 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path("create", views.create_model), - path("update/", views.update_model), - path("delete/", views.delete_model), - - path("start//", views.start_annotation), - path("check/", views.check), - path("cancel/", views.cancel), - - path("meta/get", views.get_meta_info), -] diff --git a/cvat/apps/auto_annotation/views.py b/cvat/apps/auto_annotation/views.py deleted file mode 100644 index c521424dfde9..000000000000 --- a/cvat/apps/auto_annotation/views.py +++ /dev/null @@ -1,265 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import django_rq -import json -import os - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest -from rest_framework.decorators import api_view -from django.db.models import Q -from rules.contrib.views import permission_required, objectgetter - -from cvat.apps.authentication.decorators import login_required -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.authentication.auth import has_admin_role -from cvat.apps.engine.log import slogger - -from .model_loader import load_labelmap -from . import model_manager -from .models import AnnotationModel - -@login_required -@permission_required(perm=["engine.task.change"], - fn=objectgetter(TaskModel, "tid"), raise_exception=True) -def cancel(request, tid): - try: - queue = django_rq.get_queue("low") - job = queue.fetch_job("auto_annotation.run.{}".format(tid)) - if job is None or job.is_finished or job.is_failed: - raise Exception("Task is not being annotated currently") - elif "cancel" not in job.meta: - job.meta["cancel"] = True - job.save() - - except Exception as ex: - try: - slogger.task[tid].exception("cannot cancel auto annotation for task #{}".format(tid), exc_info=True) - except Exception as logger_ex: - slogger.glob.exception("exception was occured during cancel auto annotation request for task {}: {}".format(tid, str(logger_ex)), exc_info=True) - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() - -@login_required -@permission_required(perm=["auto_annotation.model.create"], raise_exception=True) -def create_model(request): - if request.method != 'POST': - return HttpResponseBadRequest("Only POST requests are accepted") - - try: - params = request.POST - storage = params["storage"] - name = params["name"] - is_shared = params["shared"].lower() == "true" - if is_shared and not has_admin_role(request.user): - raise Exception("Only admin can create shared models") - - files = request.FILES if storage == "local" else params - model = files["xml"] - weights = files["bin"] - labelmap = files["json"] - interpretation_script = files["py"] - owner = request.user - - rq_id = model_manager.create_or_update( - dl_model_id=None, - name=name, - model_file=model, - weights_file=weights, - labelmap_file=labelmap, - interpretation_file=interpretation_script, - owner=owner, - storage=storage, - is_shared=is_shared, - ) - - return JsonResponse({"id": rq_id}) - except Exception as e: - return HttpResponseBadRequest(str(e)) - -@login_required -@permission_required(perm=["auto_annotation.model.update"], - fn=objectgetter(AnnotationModel, "mid"), raise_exception=True) -def update_model(request, mid): - if request.method != 'POST': - return HttpResponseBadRequest("Only POST requests are accepted") - - try: - params = request.POST - storage = params["storage"] - name = params.get("name") - is_shared = params.get("shared") - is_shared = is_shared.lower() == "true" if is_shared else None - if is_shared and not has_admin_role(request.user): - raise Exception("Only admin can create shared models") - files = request.FILES - model = files.get("xml") - weights = files.get("bin") - labelmap = files.get("json") - interpretation_script = files.get("py") - - rq_id = model_manager.create_or_update( - dl_model_id=mid, - name=name, - model_file=model, - weights_file=weights, - labelmap_file=labelmap, - interpretation_file=interpretation_script, - owner=None, - storage=storage, - is_shared=is_shared, - ) - - return JsonResponse({"id": rq_id}) - except Exception as e: - return HttpResponseBadRequest(str(e)) - -@login_required -@permission_required(perm=["auto_annotation.model.delete"], - fn=objectgetter(AnnotationModel, "mid"), raise_exception=True) -def delete_model(request, mid): - if request.method != 'DELETE': - return HttpResponseBadRequest("Only DELETE requests are accepted") - model_manager.delete(mid) - return HttpResponse() - -@api_view(['POST']) -@login_required -def get_meta_info(request): - try: - tids = request.data - response = { - "admin": has_admin_role(request.user), - "models": [], - "run": {}, - } - dl_model_list = list(AnnotationModel.objects.filter(Q(owner=request.user) | Q(primary=True) | Q(shared=True)).order_by('-created_date')) - for dl_model in dl_model_list: - labels = [] - if dl_model.labelmap_file and os.path.exists(dl_model.labelmap_file.name): - with dl_model.labelmap_file.open('r') as f: - labels = list(json.load(f)["label_map"].values()) - - response["models"].append({ - "id": dl_model.id, - "name": dl_model.name, - "primary": dl_model.primary, - "uploadDate": dl_model.created_date, - "updateDate": dl_model.updated_date, - "labels": labels, - "owner": dl_model.owner.id, - }) - - queue = django_rq.get_queue("low") - for tid in tids: - rq_id = "auto_annotation.run.{}".format(tid) - job = queue.fetch_job(rq_id) - if job is not None: - response["run"][tid] = { - "status": job.get_status(), - "rq_id": rq_id, - } - - return JsonResponse(response) - except Exception as e: - return HttpResponseBadRequest(str(e)) - -@login_required -@permission_required(perm=["engine.task.change"], - fn=objectgetter(TaskModel, "tid"), raise_exception=True) -@permission_required(perm=["auto_annotation.model.access"], - fn=objectgetter(AnnotationModel, "mid"), raise_exception=True) -def start_annotation(request, mid, tid): - slogger.glob.info("auto annotation create request for task {} via DL model {}".format(tid, mid)) - try: - db_task = TaskModel.objects.get(pk=tid) - queue = django_rq.get_queue("low") - job = queue.fetch_job("auto_annotation.run.{}".format(tid)) - if job is not None and (job.is_started or job.is_queued): - raise Exception("The process is already running") - - data = json.loads(request.body.decode('utf-8')) - - should_reset = data["reset"] - user_defined_labels_mapping = data["labels"] - - dl_model = AnnotationModel.objects.get(pk=mid) - - model_file_path = dl_model.model_file.name - weights_file_path = dl_model.weights_file.name - labelmap_file = dl_model.labelmap_file.name - convertation_file_path = dl_model.interpretation_file.name - restricted = not has_admin_role(dl_model.owner) - - db_labels = db_task.label_set.prefetch_related("attributespec_set").all() - db_attributes = {db_label.id: - {db_attr.name: db_attr.id for db_attr in db_label.attributespec_set.all()} for db_label in db_labels} - db_labels = {db_label.name:db_label.id for db_label in db_labels} - - model_labels = {value: key for key, value in load_labelmap(labelmap_file).items()} - - labels_mapping = {} - for user_model_label, user_db_label in user_defined_labels_mapping.items(): - if user_model_label in model_labels and user_db_label in db_labels: - labels_mapping[int(model_labels[user_model_label])] = db_labels[user_db_label] - - if not labels_mapping: - raise Exception("No labels found for annotation") - - rq_id="auto_annotation.run.{}".format(tid) - queue.enqueue_call(func=model_manager.run_inference_thread, - args=( - tid, - model_file_path, - weights_file_path, - labels_mapping, - db_attributes, - convertation_file_path, - should_reset, - request.user, - restricted, - ), - job_id = rq_id, - timeout=604800) # 7 days - - slogger.task[tid].info("auto annotation job enqueued") - - except Exception as ex: - try: - slogger.task[tid].exception("exception was occurred during annotation request", exc_info=True) - except Exception as logger_ex: - slogger.glob.exception("exception was occurred during create auto annotation request for task {}: {}".format(tid, str(logger_ex)), exc_info=True) - return HttpResponseBadRequest(str(ex)) - - return JsonResponse({"id": rq_id}) - -@login_required -def check(request, rq_id): - try: - target_queue = "low" if "auto_annotation.run" in rq_id else "default" - queue = django_rq.get_queue(target_queue) - job = queue.fetch_job(rq_id) - if job is not None and "cancel" in job.meta: - return JsonResponse({"status": "finished"}) - data = {} - if job is None: - data["status"] = "unknown" - elif job.is_queued: - data["status"] = "queued" - elif job.is_started: - data["status"] = "started" - data["progress"] = job.meta["progress"] if "progress" in job.meta else "" - elif job.is_finished: - data["status"] = "finished" - job.delete() - else: - data["status"] = "failed" - data["error"] = job.exc_info - job.delete() - - except Exception: - data["status"] = "unknown" - - return JsonResponse(data) diff --git a/cvat/apps/documentation/installation.md b/cvat/apps/documentation/installation.md index 0fb2a2ae5706..d9083b84229d 100644 --- a/cvat/apps/documentation/installation.md +++ b/cvat/apps/documentation/installation.md @@ -242,7 +242,6 @@ server. Proxy is an advanced topic and it is not covered by the guide. ### Additional components -- [Auto annotation using DL models in OpenVINO toolkit format](/cvat/apps/auto_annotation/README.md) - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) - [Semi-automatic segmentation with Deep Extreme Cut](/cvat/apps/dextr_segmentation/README.md) - [Auto segmentation: Keras+Tensorflow Mask R-CNN Segmentation](/components/auto_segmentation/README.md) @@ -515,7 +514,7 @@ server { proxy_set_header Host $http_host; proxy_pass_header Set-Cookie; - location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|dextr/.*|reid/.* { + location ~* /api/.*|git/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|reid/.* { proxy_pass http://cvat:8080; } diff --git a/cvat/settings/base.py b/cvat/settings/base.py index ceca1494957a..514ed32d8a5c 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -161,9 +161,6 @@ def generate_ssh_keys(): 'REGISTER_SERIALIZER': 'cvat.apps.restrictions.serializers.RestrictedRegisterSerializer' } -if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no'): - INSTALLED_APPS += ['cvat.apps.auto_annotation'] - if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no') and os.environ.get('REID_MODEL_DIR', ''): INSTALLED_APPS += ['cvat.apps.reid'] diff --git a/cvat/urls.py b/cvat/urls.py index 42346edf1dfe..376ff7e55a50 100644 --- a/cvat/urls.py +++ b/cvat/urls.py @@ -37,9 +37,6 @@ if apps.is_installed('cvat.apps.reid'): urlpatterns.append(path('reid/', include('cvat.apps.reid.urls'))) -if apps.is_installed('cvat.apps.auto_annotation'): - urlpatterns.append(path('auto_annotation/', include('cvat.apps.auto_annotation.urls'))) - if apps.is_installed('cvat.apps.log_viewer'): urlpatterns.append(path('analytics/', include('cvat.apps.log_viewer.urls'))) diff --git a/cvat_proxy/conf.d/cvat.conf.template b/cvat_proxy/conf.d/cvat.conf.template index 8e20750866a8..0ff987c4624f 100644 --- a/cvat_proxy/conf.d/cvat.conf.template +++ b/cvat_proxy/conf.d/cvat.conf.template @@ -12,7 +12,7 @@ server { proxy_set_header Host $http_host; proxy_pass_header Set-Cookie; - location ~* /api/.*|git/.*|tensorflow/.*|auto_annotation/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|reid/.* { + location ~* /api/.*|git/.*|analytics/.*|static/.*|admin|admin/.*|documentation/.*|reid/.* { proxy_pass http://cvat:8080; } diff --git a/utils/README.md b/utils/README.md index a768eedf7400..48b87c96e416 100644 --- a/utils/README.md +++ b/utils/README.md @@ -3,6 +3,6 @@ ## Description -This folder contains some useful utilities for Computer Vision Annotation Tool (CVAT). To read about a certain utility please choose a link: - [Auto Annotation Runner](auto_annotation/README.md) +This folder contains some useful utilities for Computer Vision Annotation Tool (CVAT). +To read about a certain utility please choose a link: - [Command line interface for working with CVAT tasks](cli/README.md) diff --git a/utils/auto_annotation/README.md b/utils/auto_annotation/README.md deleted file mode 100644 index 4bc646543053..000000000000 --- a/utils/auto_annotation/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# Auto Annotation Runner - -A small command line program to test and run AutoAnnotation Scripts. - -## Instructions - -There are two modes to run this script in. If you already have a model uploaded into the server, and you're having -issues with running it in production, you can pass in the model name and a task id that you want to test against. - -```shell -# Note that this module can be found in cvat/utils/auto_annotation/run_model.py -$ python /path/to/run_model.py --model-name mymodel --task-id 4 -``` - -If you're running in docker, this can be useful way to debug your model. - -``` shell -$ docker exec -it cvat bash -ic 'python3 ~/cvat/apps/auto_annotation/run_model.py --model-name my-model --task-id 4 -``` - -If you are developing an auto annotation model or you can't get something uploaded into the server, -then you'll need to specify the individual inputs. - -```shell -# Note that this module can be found in cvat/utils/auto_annotation/run_model.py -$ python path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json -``` - -Some programs need to run unrestricted or as an administer. Use the `--unrestriced` flag to simulate. - -You can pass image files in to fully simulate your findings. Images are passed in as a list - -```shell -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg -``` - -Additionally, it's sometimes useful to visualize your images. -Use the `--show-images` flag to have each image with the annotations pop up. - -```shell -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --show-images -``` - -If you'd like to see the labels printed on the image, use the `--show-labels` flag - -```shell -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --show-images \ - --show-labels -``` - -There's a command that let's you scan quickly by setting the length of time (in milliseconds) to display each image. -Use the `--show-image-delay` flag and set the appropriate time. -In this example, 2000 milliseconds is 2 seconds for each image. - -```shell -# Display each image in a window for 2 seconds -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --show-images \ - --show-image-delay 2000 -``` - -Visualization isn't always enough. -The CVAT has a serialization step that can throw errors on model upload even after successful visualization. -You must install the necessary packages installed, but then you can add the `--serialize` command to ensure that your -results will serialize correctly. - -```shell -$ python /path/to/run_model.py --py /path/to/python/interp.py \ - --xml /path/to/xml/file.xml \ - --bin /path/to/bin/file.bin \ - --json /path/to/json/mapping/mapping.json \ - --image-files /path/to/img.jpg /path2/to/img2.png /path/to/img3.jpg \ - --serialize -``` diff --git a/utils/auto_annotation/run_model.py b/utils/auto_annotation/run_model.py deleted file mode 100644 index 9df63528ed4d..000000000000 --- a/utils/auto_annotation/run_model.py +++ /dev/null @@ -1,263 +0,0 @@ -import os -import sys -import json -import argparse -import random -import logging -import fnmatch -from operator import xor - -import numpy as np -import cv2 - -work_dir = os.path.dirname(os.path.abspath(__file__)) -cvat_dir = os.path.join(work_dir, '..', '..') - -sys.path.insert(0, cvat_dir) - -from cvat.apps.auto_annotation.inference import run_inference_engine_annotation - - -def _get_kwargs(): - parser = argparse.ArgumentParser() - parser.add_argument('--py', help='Path to the python interpt file') - parser.add_argument('--xml', help='Path to the xml file') - parser.add_argument('--bin', help='Path to the bin file') - parser.add_argument('--json', help='Path to the JSON mapping file') - - parser.add_argument('--model-name', help='Name of the model in the Model Manager') - parser.add_argument('--task-id', type=int, help='ID task used to test the model') - - parser.add_argument('--restricted', dest='restricted', action='store_true') - parser.add_argument('--unrestricted', dest='restricted', action='store_false') - parser.add_argument('--image-files', nargs='*', help='Paths to image files you want to test') - - parser.add_argument('--show-images', action='store_true', help='Show the results of the annotation in a window') - parser.add_argument('--show-image-delay', default=0, type=int, help='Displays the images for a set duration in milliseconds, default is until a key is pressed') - parser.add_argument('--serialize', default=False, action='store_true', help='Try to serialize the result') - parser.add_argument('--show-labels', action='store_true', help='Show the labels on the window') - - return vars(parser.parse_args()) - -def _init_django(settings): - import django - os.environ['DJANGO_SETTINGS_MODULE'] = settings - django.setup() - -def random_color(): - rgbl=[255,0,0] - random.shuffle(rgbl) - return tuple(rgbl) - - -def pairwise(iterable): - result = [] - for i in range(0, len(iterable) - 1, 2): - result.append((iterable[i], iterable[i+1])) - return np.array(result, dtype=np.int32) - -def find_min_y(array): - min_ = sys.maxsize - index = None - for i, pair in enumerate(array): - if pair[1] < min_: - min_ = pair[1] - index = i - - return array[index] - -def _get_docker_files(model_name: str, task_id: int): - _init_django('cvat.settings.development') - - from cvat.apps.auto_annotation.models import AnnotationModel - from cvat.apps.engine.models import Task as TaskModel - - task = TaskModel(pk=task_id) - model = AnnotationModel.objects.get(name=model_name) - - images_dir = task.data.get_data_dirname() - - py_file = model.interpretation_file.name - mapping_file = model.labelmap_file.name - xml_file = model.model_file.name - bin_file = model.weights_file.name - - image_files = [] - images_dir = os.path.abspath(images_dir) - for root, _, filenames in os.walk(images_dir): - for filename in fnmatch.filter(filenames, '*.jpg'): - image_files.append(os.path.join(root, filename)) - - return py_file, mapping_file, bin_file, xml_file, image_files - - -def main(): - kwargs = _get_kwargs() - - py_file = kwargs.get('py') - bin_file = kwargs.get('bin') - mapping_file = os.path.abspath(kwargs.get('json')) - xml_file = kwargs.get('xml') - - model_name = kwargs.get('model_name') - task_id = kwargs.get('task_id') - - is_docker = model_name and task_id - - # xor is `exclusive or`. English is: if one or the other but not both - if xor(bool(model_name), bool(task_id)): - logging.critical('Must provide both `--model-name` and `--task-id` together!') - return - - if is_docker: - files = _get_docker_files(model_name, task_id) - py_file = files[0] - mapping_file = files[1] - bin_file = files[2] - xml_file = files[3] - image_files = files[4] - else: - return_ = False - if not py_file: - logging.critical('Must provide --py file!') - return_ = True - if not bin_file: - logging.critical('Must provide --bin file!') - return_ = True - if not xml_file: - logging.critical('Must provide --xml file!') - return_ = True - if not mapping_file: - logging.critical('Must provide --json file!') - return_ = True - - if return_: - return - - if not os.path.isfile(py_file): - logging.critical('Py file not found! Check the path') - return - - if not os.path.isfile(bin_file): - logging.critical('Bin file is not found! Check path!') - return - - if not os.path.isfile(xml_file): - logging.critical('XML File not found! Check path!') - return - - if not os.path.isfile(mapping_file): - logging.critical('JSON file is not found! Check path!') - return - - with open(mapping_file) as json_file: - try: - mapping = json.load(json_file) - except json.decoder.JSONDecodeError: - logging.critical('JSON file not able to be parsed! Check file') - return - - try: - mapping = mapping['label_map'] - except KeyError: - logging.critical("JSON Mapping file must contain key `label_map`!") - logging.critical("Exiting") - return - - mapping = {int(k): v for k, v in mapping.items()} - - restricted = kwargs['restricted'] - - if not is_docker: - image_files = kwargs.get('image_files') - - if image_files: - image_data = [cv2.imread(f) for f in image_files] - else: - test_image = np.ones((1024, 1980, 3), np.uint8) * 255 - image_data = [test_image,] - attribute_spec = {} - - results = run_inference_engine_annotation(image_data, - xml_file, - bin_file, - mapping, - attribute_spec, - py_file, - restricted=restricted) - - - logging.warning('Inference didn\'t have any errors.') - show_images = kwargs.get('show_images', False) - - if show_images: - if image_files is None: - logging.critical("Warning, no images provided!") - logging.critical('Exiting without presenting results') - return - - if not results['shapes']: - logging.warning(str(results)) - logging.critical("No objects detected!") - return - - show_image_delay = kwargs['show_image_delay'] - show_labels = kwargs.get('show_labels') - - for index, data in enumerate(image_data): - for detection in results['shapes']: - if not detection['frame'] == index: - continue - points = detection['points'] - label_str = detection['label_id'] - - # Cv2 doesn't like floats for drawing - points = [int(p) for p in points] - color = random_color() - - if detection['type'] == 'rectangle': - cv2.rectangle(data, (points[0], points[1]), (points[2], points[3]), color, 3) - - if show_labels: - cv2.putText(data, label_str, (points[0], points[1] - 7), cv2.FONT_HERSHEY_COMPLEX, 0.6, color, 1) - - elif detection['type'] in ('polygon', 'polyline'): - # polylines is picky about datatypes - points = pairwise(points) - cv2.polylines(data, [points], 1, color) - - if show_labels: - min_point = find_min_y(points) - cv2.putText(data, label_str, (min_point[0], min_point[1] - 7), cv2.FONT_HERSHEY_COMPLEX, 0.6, color, 1) - - cv2.imshow(str(index), data) - cv2.waitKey(show_image_delay) - cv2.destroyWindow(str(index)) - - if kwargs['serialize']: - _init_django('cvat.settings.production') - - from cvat.apps.engine.serializers import LabeledDataSerializer - - # NOTE: We're actually using `run_inference_engine_annotation` - # incorrectly here. The `mapping` dict is supposed to be a mapping - # of integers -> integers and represents the transition from model - # integers to the labels in the database. We're using a mapping of - # integers -> strings. For testing purposes, this shortcut is fine. - # We just want to make sure everything works. Until, that is.... - # we want to test using the label serializer. Then we have to transition - # back to integers, otherwise the serializer complains about have a string - # where an integer is expected. We'll just brute force that. - - for shape in results['shapes']: - # Change the english label to an integer for serialization validation - shape['label_id'] = 1 - - serializer = LabeledDataSerializer(data=results) - - if not serializer.is_valid(): - logging.critical('Data unable to be serialized correctly!') - serializer.is_valid(raise_exception=True) - -if __name__ == '__main__': - main() diff --git a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/README.md b/utils/open_model_zoo/faster_rcnn_inception_v2_coco/README.md deleted file mode 100644 index a6ede4453a92..000000000000 --- a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Faster R-CNN with Inception v2 (https://arxiv.org/pdf/1801.04381.pdf) pre-trained on the COCO dataset - -### What is it? -* This application allows you automatically to annotate many various objects on images. -* It uses [Faster RCNN Inception Resnet v2 Atrous Coco Model](http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz) from [tensorflow detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md) -* It can work on CPU (with Tensorflow or OpenVINO) or GPU (with Tensorflow GPU). diff --git a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/interp.py b/utils/open_model_zoo/faster_rcnn_inception_v2_coco/interp.py deleted file mode 100644 index f8a8a60219f0..000000000000 --- a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/interp.py +++ /dev/null @@ -1,19 +0,0 @@ -threshold = .5 - -for detection in detections: - frame_number = detection['frame_id'] - height = detection['frame_height'] - width = detection['frame_width'] - detection = detection['detections'] - - prediction = detection[0][0] - for obj in prediction: - obj_class = int(obj[1]) - obj_value = obj[2] - if obj_value >= threshold: - x = obj[3] * width - y = obj[4] * height - right = obj[5] * width - bottom = obj[6] * height - - results.add_box(x, y, right, bottom, obj_class, frame_number) diff --git a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/mapping.json b/utils/open_model_zoo/faster_rcnn_inception_v2_coco/mapping.json deleted file mode 100644 index 3efdb307565f..000000000000 --- a/utils/open_model_zoo/faster_rcnn_inception_v2_coco/mapping.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "label_map": { - "1": "person", - "2": "bicycle", - "3": "car", - "4": "motorcycle", - "5": "airplane", - "6": "bus", - "7": "train", - "8": "truck", - "9": "boat", - "10": "traffic_light", - "11": "fire_hydrant", - "13": "stop_sign", - "14": "parking_meter", - "15": "bench", - "16": "bird", - "17": "cat", - "18": "dog", - "19": "horse", - "20": "sheep", - "21": "cow", - "22": "elephant", - "23": "bear", - "24": "zebra", - "25": "giraffe", - "27": "backpack", - "28": "umbrella", - "31": "handbag", - "32": "tie", - "33": "suitcase", - "34": "frisbee", - "35": "skis", - "36": "snowboard", - "37": "sports_ball", - "38": "kite", - "39": "baseball_bat", - "40": "baseball_glove", - "41": "skateboard", - "42": "surfboard", - "43": "tennis_racket", - "44": "bottle", - "46": "wine_glass", - "47": "cup", - "48": "fork", - "49": "knife", - "50": "spoon", - "51": "bowl", - "52": "banana", - "53": "apple", - "54": "sandwich", - "55": "orange", - "56": "broccoli", - "57": "carrot", - "58": "hot_dog", - "59": "pizza", - "60": "donut", - "61": "cake", - "62": "chair", - "63": "couch", - "64": "potted_plant", - "65": "bed", - "67": "dining_table", - "70": "toilet", - "72": "tv", - "73": "laptop", - "74": "mouse", - "75": "remote", - "76": "keyboard", - "77": "cell_phone", - "78": "microwave", - "79": "oven", - "80": "toaster", - "81": "sink", - "83": "refrigerator", - "84": "book", - "85": "clock", - "86": "vase", - "87": "scissors", - "88": "teddy_bear", - "89": "hair_drier", - "90": "toothbrush" - } -} diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md deleted file mode 100644 index be7a82121bf0..000000000000 --- a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# mask_rcnn_inception_resnet_v2_atrous_coco - -## Use Case and High-Level Description - -Mask R-CNN Inception Resnet V2 Atrous is trained on COCO dataset and used for object instance segmentation. -For details, see a [paper](https://arxiv.org/pdf/1703.06870.pdf). - -## Specification - -| Metric | Value | -|---------------------------------|-------------------------------------------| -| Type | Instance segmentation | -| GFlops | 675.314 | -| MParams | 92.368 | -| Source framework | TensorFlow\* | - -## Legal Information - -[https://raw.githubusercontent.com/tensorflow/models/master/LICENSE]() - -## OpenVINO Conversion Notes - -In order to convert the code into the openvino format, please see the [following link](https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_Object_Detection_API_Models.html#mask_r_cnn_topologies). - -The conversion command from the command line prompt will look something like the following. - -```shell -$ python /opt/intel/openvino/deployment_tools/model_optimizer/mo_tf.py \ - --input_model /path/to/frozen_inference_graph.pb \ - --tensorflow_use_custom_operations_config /opt/intel/openvino/deployment_tools/model_optimizer/extensions/front/tf/mask_rcnn_support.json \ - --tensorflow_object_detection_api_pipeline_config /path/to/pipeline.config -``` diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py deleted file mode 100644 index 6625a8349330..000000000000 --- a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/interp.py +++ /dev/null @@ -1,64 +0,0 @@ -import numpy as np -import cv2 -from skimage.measure import approximate_polygon, find_contours - - -MASK_THRESHOLD = .5 -PROBABILITY_THRESHOLD = 0.2 - - -# Ref: https://software.intel.com/en-us/forums/computer-vision/topic/804895 -def segm_postprocess(box: list, raw_cls_mask, im_h, im_w, threshold): - ymin, xmin, ymax, xmax = box - - width = int(abs(xmax - xmin)) - height = int(abs(ymax - ymin)) - - result = np.zeros((im_h, im_w), dtype=np.uint8) - resized_mask = cv2.resize(raw_cls_mask, dsize=(height, width), interpolation=cv2.INTER_CUBIC) - - # extract the ROI of the image - ymin = int(round(ymin)) - xmin = int(round(xmin)) - ymax = ymin + height - xmax = xmin + width - result[xmin:xmax, ymin:ymax] = (resized_mask>threshold).astype(np.uint8) * 255 - - return result - - -for detection in detections: - frame_number = detection['frame_id'] - height = detection['frame_height'] - width = detection['frame_width'] - detection = detection['detections'] - - masks = detection['masks'] - boxes = detection['reshape_do_2d'] - - for index, box in enumerate(boxes): - label = int(box[1]) - obj_value = box[2] - if obj_value >= PROBABILITY_THRESHOLD: - x = box[3] * width - y = box[4] * height - right = box[5] * width - bottom = box[6] * height - mask = masks[index][label - 1] - - mask = segm_postprocess((x, y, right, bottom), - mask, - height, - width, - MASK_THRESHOLD) - - contours = find_contours(mask, MASK_THRESHOLD) - contour = contours[0] - contour = np.flip(contour, axis=1) - contour = approximate_polygon(contour, tolerance=2.5) - segmentation = contour.tolist() - - - # NOTE: if you want to see the boxes, uncomment next line - # results.add_box(x, y, right, bottom, label, frame_number) - results.add_polygon(segmentation, label, frame_number) diff --git a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json b/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json deleted file mode 100644 index 3efdb307565f..000000000000 --- a/utils/open_model_zoo/mask_rcnn_inception_resnet_v2_atrous_coco/mapping.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "label_map": { - "1": "person", - "2": "bicycle", - "3": "car", - "4": "motorcycle", - "5": "airplane", - "6": "bus", - "7": "train", - "8": "truck", - "9": "boat", - "10": "traffic_light", - "11": "fire_hydrant", - "13": "stop_sign", - "14": "parking_meter", - "15": "bench", - "16": "bird", - "17": "cat", - "18": "dog", - "19": "horse", - "20": "sheep", - "21": "cow", - "22": "elephant", - "23": "bear", - "24": "zebra", - "25": "giraffe", - "27": "backpack", - "28": "umbrella", - "31": "handbag", - "32": "tie", - "33": "suitcase", - "34": "frisbee", - "35": "skis", - "36": "snowboard", - "37": "sports_ball", - "38": "kite", - "39": "baseball_bat", - "40": "baseball_glove", - "41": "skateboard", - "42": "surfboard", - "43": "tennis_racket", - "44": "bottle", - "46": "wine_glass", - "47": "cup", - "48": "fork", - "49": "knife", - "50": "spoon", - "51": "bowl", - "52": "banana", - "53": "apple", - "54": "sandwich", - "55": "orange", - "56": "broccoli", - "57": "carrot", - "58": "hot_dog", - "59": "pizza", - "60": "donut", - "61": "cake", - "62": "chair", - "63": "couch", - "64": "potted_plant", - "65": "bed", - "67": "dining_table", - "70": "toilet", - "72": "tv", - "73": "laptop", - "74": "mouse", - "75": "remote", - "76": "keyboard", - "77": "cell_phone", - "78": "microwave", - "79": "oven", - "80": "toaster", - "81": "sink", - "83": "refrigerator", - "84": "book", - "85": "clock", - "86": "vase", - "87": "scissors", - "88": "teddy_bear", - "89": "hair_drier", - "90": "toothbrush" - } -} diff --git a/utils/open_model_zoo/yolov3/README.md b/utils/open_model_zoo/yolov3/README.md deleted file mode 100644 index 2e47953cb3f8..000000000000 --- a/utils/open_model_zoo/yolov3/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Object Detection YOLO V3 Python Demo, Async API Performance Showcase - -See [these instructions][1] for converting the yolo weights to the OpenVino format. - -As of OpenVINO 2019 R3, only tensorflow 1.13 and NetworkX 2.3. -These can be explicitly installed using the following command. - -```bash -python3 -m pip install tensorflow==1.13 networkx==2.3 -``` - - -Additionally, at the time of writing, the model optimizer required an input shape. - -``` bash -python3 mo_tf.py \ - --input_model /path/to/yolo_v3.pb \ - --tensorflow_use_custom_operations_config $MO_ROOT/extensions/front/tf/yolo_v3.json \ - --input_shape [1,416,416,3] -``` - -[1]: https://docs.openvinotoolkit.org/latest/_docs_MO_DG_prepare_model_convert_model_tf_specific_Convert_YOLO_From_Tensorflow.html diff --git a/utils/open_model_zoo/yolov3/interp.py b/utils/open_model_zoo/yolov3/interp.py deleted file mode 100644 index 4c76c85448d0..000000000000 --- a/utils/open_model_zoo/yolov3/interp.py +++ /dev/null @@ -1,160 +0,0 @@ -from math import exp - - -class Parser: - IOU_THRESHOLD = 0.4 - PROB_THRESHOLD = 0.5 - - def __init__(self): - self.objects = [] - - def scale_bbox(self, x, y, h, w, class_id, confidence, h_scale, w_scale): - xmin = int((x - w / 2) * w_scale) - ymin = int((y - h / 2) * h_scale) - xmax = int(xmin + w * w_scale) - ymax = int(ymin + h * h_scale) - - return dict(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, class_id=class_id, confidence=confidence) - - def entry_index(self, side, coord, classes, location, entry): - side_power_2 = side ** 2 - n = location // side_power_2 - loc = location % side_power_2 - return int(side_power_2 * (n * (coord + classes + 1) + entry) + loc) - - def intersection_over_union(self, box_1, box_2): - width_of_overlap_area = min(box_1['xmax'], box_2['xmax']) - max(box_1['xmin'], box_2['xmin']) - height_of_overlap_area = min(box_1['ymax'], box_2['ymax']) - max(box_1['ymin'], box_2['ymin']) - if width_of_overlap_area < 0 or height_of_overlap_area < 0: - area_of_overlap = 0 - else: - area_of_overlap = width_of_overlap_area * height_of_overlap_area - box_1_area = (box_1['ymax'] - box_1['ymin']) * (box_1['xmax'] - box_1['xmin']) - box_2_area = (box_2['ymax'] - box_2['ymin']) * (box_2['xmax'] - box_2['xmin']) - area_of_union = box_1_area + box_2_area - area_of_overlap - if area_of_union == 0: - return 0 - return area_of_overlap / area_of_union - - - def sort_objects(self): - self.objects = sorted(self.objects, key=lambda obj : obj['confidence'], reverse=True) - - for i in range(len(self.objects)): - if self.objects[i]['confidence'] == 0: - continue - for j in range(i + 1, len(self.objects)): - if self.intersection_over_union(self.objects[i], self.objects[j]) > self.IOU_THRESHOLD: - self.objects[j]['confidence'] = 0 - - def parse_yolo_region(self, blob: 'np.ndarray', original_shape: list, params: dict) -> list: - - # YOLO magic numbers - # See: https://github.com/opencv/open_model_zoo/blob/acf297c73db8cb3f68791ae1fad4a7cc4a6039e5/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L61 - num = 3 - coords = 4 - classes = 80 - # ----------------- - - _, _, out_blob_h, out_blob_w = blob.shape - assert out_blob_w == out_blob_h, "Invalid size of output blob. It sould be in NCHW layout and height should " \ - "be equal to width. Current height = {}, current width = {}" \ - "".format(out_blob_h, out_blob_w) - - # ------ Extracting layer parameters -- - orig_im_h, orig_im_w = original_shape - predictions = blob.flatten() - side_square = params['side'] * params['side'] - - # ------ Parsing YOLO Region output -- - for i in range(side_square): - row = i // params['side'] - col = i % params['side'] - for n in range(num): - # -----entry index calcs------ - obj_index = self.entry_index(params['side'], coords, classes, n * side_square + i, coords) - scale = predictions[obj_index] - if scale < self.PROB_THRESHOLD: - continue - box_index = self.entry_index(params['side'], coords, classes, n * side_square + i, 0) - - # Network produces location predictions in absolute coordinates of feature maps. - # Scale it to relative coordinates. - x = (col + predictions[box_index + 0 * side_square]) / params['side'] * 416 - y = (row + predictions[box_index + 1 * side_square]) / params['side'] * 416 - # Value for exp is very big number in some cases so following construction is using here - try: - h_exp = exp(predictions[box_index + 3 * side_square]) - w_exp = exp(predictions[box_index + 2 * side_square]) - except OverflowError: - continue - - w = w_exp * params['anchors'][2 * n] - h = h_exp * params['anchors'][2 * n + 1] - - for j in range(classes): - class_index = self.entry_index(params['side'], coords, classes, n * side_square + i, - coords + 1 + j) - confidence = scale * predictions[class_index] - if confidence < self.PROB_THRESHOLD: - continue - - self.objects.append(self.scale_bbox(x=x, - y=y, - h=h, - w=w, - class_id=j, - confidence=confidence, - h_scale=(orig_im_h/416), - w_scale=(orig_im_w/416))) - - -for detection in detections: - frame_number = detection['frame_id'] - height = detection['frame_height'] - width = detection['frame_width'] - detection = detection['detections'] - - original_shape = (height, width) - - # https://github.com/opencv/open_model_zoo/blob/master/demos/python_demos/object_detection_demo_yolov3_async/object_detection_demo_yolov3_async.py#L72 - anchors = [10,13,16,30,33,23,30,61,62,45,59,119,116,90,156,198,373,326] - conv_6 = {'side': 13, 'mask': [6,7,8]} - conv_14 = {'side': 26, 'mask': [3,4,5]} - conv_22 = {'side': 52, 'mask': [0,1,2]} - - yolo_params = {'detector/yolo-v3/Conv_6/BiasAdd/YoloRegion': conv_6, - 'detector/yolo-v3/Conv_14/BiasAdd/YoloRegion': conv_14, - 'detector/yolo-v3/Conv_22/BiasAdd/YoloRegion': conv_22} - - for conv_net in yolo_params.values(): - mask = conv_net['mask'] - masked_anchors = [] - for idx in mask: - masked_anchors += [anchors[idx * 2], anchors[idx * 2 + 1]] - - conv_net['anchors'] = masked_anchors - - parser = Parser() - - for name, blob in detection.items(): - parser.parse_yolo_region(blob, original_shape, yolo_params[name]) - - parser.sort_objects() - - objects = [] - for obj in parser.objects: - if obj['confidence'] >= parser.PROB_THRESHOLD: - label = obj['class_id'] - xmin = obj['xmin'] - xmax = obj['xmax'] - ymin = obj['ymin'] - ymax = obj['ymax'] - - # Enforcing extra checks for bounding box coordinates - xmin = max(0,xmin) - ymin = max(0,ymin) - xmax = min(xmax,width) - ymax = min(ymax,height) - - results.add_box(xmin, ymin, xmax, ymax, label, frame_number) diff --git a/utils/open_model_zoo/yolov3/mapping.json b/utils/open_model_zoo/yolov3/mapping.json deleted file mode 100644 index bfb65a24cf0c..000000000000 --- a/utils/open_model_zoo/yolov3/mapping.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "label_map": { - "0": "person", - "1": "bicycle", - "2": "car", - "3": "motorbike", - "4": "aeroplane", - "5": "bus", - "6": "train", - "7": "truck", - "8": "boat", - "9": "traffic light", - "10": "fire hydrant", - "11": "stop sign", - "12": "parking meter", - "13": "bench", - "14": "bird", - "15": "cat", - "16": "dog", - "17": "horse", - "18": "sheep", - "19": "cow", - "20": "elephant", - "21": "bear", - "22": "zebra", - "23": "giraffe", - "24": "backpack", - "25": "umbrella", - "26": "handbag", - "27": "tie", - "28": "suitcase", - "29": "frisbee", - "30": "skis", - "31": "snowboard", - "32": "sports ball", - "33": "kite", - "34": "baseball bat", - "35": "baseball glove", - "36": "skateboard", - "37": "surfboard", - "38": "tennis racket", - "39": "bottle", - "40": "wine glass", - "41": "cup", - "42": "fork", - "43": "knife", - "44": "spoon", - "45": "bowl", - "46": "banana", - "47": "apple", - "48": "sandwich", - "49": "orange", - "50": "broccoli", - "51": "carrot", - "52": "hot dog", - "53": "pizza", - "54": "donut", - "55": "cake", - "56": "chair", - "57": "sofa", - "58": "pottedplant", - "59": "bed", - "60": "diningtable", - "61": "toilet", - "62": "tvmonitor", - "63": "laptop", - "64": "mouse", - "65": "remote", - "66": "keyboard", - "67": "cell phone", - "68": "microwave", - "69": "oven", - "70": "toaster", - "71": "sink", - "72": "refrigerator", - "73": "book", - "74": "clock", - "75": "vase", - "76": "scissors", - "77": "teddy bear", - "78": "hair drier", - "79": "toothbrush" - } -} From a3968220c696df306297dc85d8cd9d64e0a011de Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 01:00:14 +0300 Subject: [PATCH 36/98] Remove redundant files. --- .../0001/mappings.json | 5 - .../0001/pixel_link_mobilenet_v2.py | 192 ----------------- .../0004/mappings.json | 5 - .../0004/pixel_link_mobilenet_v2.py | 197 ------------------ .../text/pixel_link_mobilenet_v2/README.md | 5 - .../semantic-segmentation-adas/interp.py | 31 --- .../semantic-segmentation-adas/mapping.json | 25 --- 7 files changed, 460 deletions(-) delete mode 100644 utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/mappings.json delete mode 100644 utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/pixel_link_mobilenet_v2.py delete mode 100644 utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json delete mode 100644 utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py delete mode 100644 utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md delete mode 100644 utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py delete mode 100644 utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/mappings.json b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/mappings.json deleted file mode 100644 index f6a0aa87964b..000000000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/mappings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label_map": { - "1": "text" - } -} diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/pixel_link_mobilenet_v2.py b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/pixel_link_mobilenet_v2.py deleted file mode 100644 index 77297cabdb18..000000000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0001/pixel_link_mobilenet_v2.py +++ /dev/null @@ -1,192 +0,0 @@ -import cv2 -import numpy as np - - -class PixelLinkDecoder(): - def __init__(self): - four_neighbours = False - if four_neighbours: - self._get_neighbours = self._get_neighbours_4 - else: - self._get_neighbours = self._get_neighbours_8 - self.pixel_conf_threshold = 0.8 - self.link_conf_threshold = 0.8 - - def decode(self, height, width, detections: dict): - self.image_height = height - self.image_width = width - self.pixel_scores = self._set_pixel_scores(detections['pixel_cls/add_2']) - self.link_scores = self._set_link_scores(detections['pixel_link/add_2']) - - self.pixel_mask = self.pixel_scores >= self.pixel_conf_threshold - self.link_mask = self.link_scores >= self.link_conf_threshold - self.points = list(zip(*np.where(self.pixel_mask))) - self.h, self.w = np.shape(self.pixel_mask) - self.group_mask = dict.fromkeys(self.points, -1) - self.bboxes = None - self.root_map = None - self.mask = None - - self._decode() - - def _softmax(self, x, axis=None): - return np.exp(x - self._logsumexp(x, axis=axis, keepdims=True)) - - def _logsumexp(self, a, axis=None, b=None, keepdims=False, return_sign=False): - if b is not None: - a, b = np.broadcast_arrays(a, b) - if np.any(b == 0): - a = a + 0. # promote to at least float - a[b == 0] = -np.inf - - a_max = np.amax(a, axis=axis, keepdims=True) - - if a_max.ndim > 0: - a_max[~np.isfinite(a_max)] = 0 - elif not np.isfinite(a_max): - a_max = 0 - - if b is not None: - b = np.asarray(b) - tmp = b * np.exp(a - a_max) - else: - tmp = np.exp(a - a_max) - - # suppress warnings about log of zero - with np.errstate(divide='ignore'): - s = np.sum(tmp, axis=axis, keepdims=keepdims) - if return_sign: - sgn = np.sign(s) - s *= sgn # /= makes more sense but we need zero -> zero - out = np.log(s) - - if not keepdims: - a_max = np.squeeze(a_max, axis=axis) - out += a_max - - if return_sign: - return out, sgn - else: - return out - - def _set_pixel_scores(self, pixel_scores): - "get softmaxed properly shaped pixel scores" - tmp = np.transpose(pixel_scores, (0, 2, 3, 1)) - return self._softmax(tmp, axis=-1)[0, :, :, 1] - - def _set_link_scores(self, link_scores): - "get softmaxed properly shaped links scores" - tmp = np.transpose(link_scores, (0, 2, 3, 1)) - tmp_reshaped = tmp.reshape(tmp.shape[:-1] + (8, 2)) - return self._softmax(tmp_reshaped, axis=-1)[0, :, :, :, 1] - - def _find_root(self, point): - root = point - update_parent = False - tmp = self.group_mask[root] - while tmp is not -1: - root = tmp - tmp = self.group_mask[root] - update_parent = True - if update_parent: - self.group_mask[point] = root - return root - - def _join(self, p1, p2): - root1 = self._find_root(p1) - root2 = self._find_root(p2) - if root1 != root2: - self.group_mask[root2] = root1 - - def _get_index(self, root): - if root not in self.root_map: - self.root_map[root] = len(self.root_map) + 1 - return self.root_map[root] - - def _get_all(self): - self.root_map = {} - self.mask = np.zeros_like(self.pixel_mask, dtype=np.int32) - - for point in self.points: - point_root = self._find_root(point) - bbox_idx = self._get_index(point_root) - self.mask[point] = bbox_idx - - def _get_neighbours_8(self, x, y): - w, h = self.w, self.h - tmp = [(0, x - 1, y - 1), (1, x, y - 1), - (2, x + 1, y - 1), (3, x - 1, y), - (4, x + 1, y), (5, x - 1, y + 1), - (6, x, y + 1), (7, x + 1, y + 1)] - - return [i for i in tmp if i[1] >= 0 and i[1] < w and i[2] >= 0 and i[2] < h] - - def _get_neighbours_4(self, x, y): - w, h = self.w, self.h - tmp = [(1, x, y - 1), - (3, x - 1, y), - (4, x + 1, y), - (6, x, y + 1)] - - return [i for i in tmp if i[1] >= 0 and i[1] < w and i[2] >= 0 and i[2] < h] - - def _mask_to_bboxes(self, min_area=300, min_height=10): - self.bboxes = [] - max_bbox_idx = self.mask.max() - mask_tmp = cv2.resize(self.mask, (self.image_width, self.image_height), interpolation=cv2.INTER_NEAREST) - - for bbox_idx in range(1, max_bbox_idx + 1): - bbox_mask = mask_tmp == bbox_idx - cnts, _ = cv2.findContours(bbox_mask.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) - if len(cnts) == 0: - continue - cnt = cnts[0] - rect, w, h = self._min_area_rect(cnt) - if min(w, h) < min_height: - continue - if w * h < min_area: - continue - self.bboxes.append(self._order_points(rect)) - - def _min_area_rect(self, cnt): - rect = cv2.minAreaRect(cnt) - w, h = rect[1] - box = cv2.boxPoints(rect) - box = np.int0(box) - return box, w, h - - def _order_points(self, rect): - """ (x, y) - Order: TL, TR, BR, BL - """ - tmp = np.zeros_like(rect) - sums = rect.sum(axis=1) - tmp[0] = rect[np.argmin(sums)] - tmp[2] = rect[np.argmax(sums)] - diff = np.diff(rect, axis=1) - tmp[1] = rect[np.argmin(diff)] - tmp[3] = rect[np.argmax(diff)] - return tmp - - def _decode(self): - for point in self.points: - y, x = point - neighbours = self._get_neighbours(x, y) - for n_idx, nx, ny in neighbours: - link_value = self.link_mask[y, x, n_idx] - pixel_cls = self.pixel_mask[ny, nx] - if link_value and pixel_cls: - self._join(point, (ny, nx)) - - self._get_all() - self._mask_to_bboxes() - - -label = 1 -pcd = PixelLinkDecoder() -for detection in detections: - frame = detection['frame_id'] - pcd.decode(detection['frame_height'], detection['frame_width'], detection['detections']) - for box in pcd.bboxes: - box = [[int(b[0]), int(b[1])] for b in box] - results.add_polygon(box, label, frame) diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json deleted file mode 100644 index f6a0aa87964b..000000000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/mappings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label_map": { - "1": "text" - } -} diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py deleted file mode 100644 index b0f105ec5a12..000000000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/0004/pixel_link_mobilenet_v2.py +++ /dev/null @@ -1,197 +0,0 @@ -# SPDX-License-Identifier: MIT` - -import cv2 -import numpy as np - - -class PixelLinkDecoder(): - def __init__(self): - four_neighbours = False - if four_neighbours: - self._get_neighbours = self._get_neighbours_4 - else: - self._get_neighbours = self._get_neighbours_8 - self.pixel_conf_threshold = 0.8 - self.link_conf_threshold = 0.8 - - def decode(self, height, width, detections: dict): - self.image_height = height - self.image_width = width - self.pixel_scores = self._set_pixel_scores(detections['model/segm_logits/add']) - self.link_scores = self._set_link_scores(detections['model/link_logits_/add']) - - self.pixel_mask = self.pixel_scores >= self.pixel_conf_threshold - self.link_mask = self.link_scores >= self.link_conf_threshold - self.points = list(zip(*np.where(self.pixel_mask))) - self.h, self.w = np.shape(self.pixel_mask) - self.group_mask = dict.fromkeys(self.points, -1) - self.bboxes = None - self.root_map = None - self.mask = None - - self._decode() - - def _softmax(self, x, axis=None): - return np.exp(x - self._logsumexp(x, axis=axis, keepdims=True)) - - # pylint: disable=no-self-use - def _logsumexp(self, a, axis=None, b=None, keepdims=False, return_sign=False): - if b is not None: - a, b = np.broadcast_arrays(a, b) - if np.any(b == 0): - a = a + 0. # promote to at least float - a[b == 0] = -np.inf - - a_max = np.amax(a, axis=axis, keepdims=True) - - if a_max.ndim > 0: - a_max[~np.isfinite(a_max)] = 0 - elif not np.isfinite(a_max): - a_max = 0 - - if b is not None: - b = np.asarray(b) - tmp = b * np.exp(a - a_max) - else: - tmp = np.exp(a - a_max) - - # suppress warnings about log of zero - with np.errstate(divide='ignore'): - s = np.sum(tmp, axis=axis, keepdims=keepdims) - if return_sign: - sgn = np.sign(s) - s *= sgn # /= makes more sense but we need zero -> zero - out = np.log(s) - - if not keepdims: - a_max = np.squeeze(a_max, axis=axis) - out += a_max - - if return_sign: - return out, sgn - else: - return out - - def _set_pixel_scores(self, pixel_scores): - "get softmaxed properly shaped pixel scores" - tmp = np.transpose(pixel_scores, (0, 2, 3, 1)) - return self._softmax(tmp, axis=-1)[0, :, :, 1] - - def _set_link_scores(self, link_scores): - "get softmaxed properly shaped links scores" - tmp = np.transpose(link_scores, (0, 2, 3, 1)) - tmp_reshaped = tmp.reshape(tmp.shape[:-1] + (8, 2)) - return self._softmax(tmp_reshaped, axis=-1)[0, :, :, :, 1] - - def _find_root(self, point): - root = point - update_parent = False - tmp = self.group_mask[root] - while tmp is not -1: - root = tmp - tmp = self.group_mask[root] - update_parent = True - if update_parent: - self.group_mask[point] = root - return root - - def _join(self, p1, p2): - root1 = self._find_root(p1) - root2 = self._find_root(p2) - if root1 != root2: - self.group_mask[root2] = root1 - - def _get_index(self, root): - if root not in self.root_map: - self.root_map[root] = len(self.root_map) + 1 - return self.root_map[root] - - def _get_all(self): - self.root_map = {} - self.mask = np.zeros_like(self.pixel_mask, dtype=np.int32) - - for point in self.points: - point_root = self._find_root(point) - bbox_idx = self._get_index(point_root) - self.mask[point] = bbox_idx - - def _get_neighbours_8(self, x, y): - w, h = self.w, self.h - tmp = [(0, x - 1, y - 1), (1, x, y - 1), - (2, x + 1, y - 1), (3, x - 1, y), - (4, x + 1, y), (5, x - 1, y + 1), - (6, x, y + 1), (7, x + 1, y + 1)] - - return [i for i in tmp if i[1] >= 0 and i[1] < w and i[2] >= 0 and i[2] < h] - - def _get_neighbours_4(self, x, y): - w, h = self.w, self.h - tmp = [(1, x, y - 1), - (3, x - 1, y), - (4, x + 1, y), - (6, x, y + 1)] - - return [i for i in tmp if i[1] >= 0 and i[1] < w and i[2] >= 0 and i[2] < h] - - def _mask_to_bboxes(self, min_area=300, min_height=10): - self.bboxes = [] - max_bbox_idx = self.mask.max() - mask_tmp = cv2.resize(self.mask, (self.image_width, self.image_height), interpolation=cv2.INTER_NEAREST) - - for bbox_idx in range(1, max_bbox_idx + 1): - bbox_mask = mask_tmp == bbox_idx - cnts, _ = cv2.findContours(bbox_mask.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) - if len(cnts) == 0: - continue - cnt = cnts[0] - rect, w, h = self._min_area_rect(cnt) - if min(w, h) < min_height: - continue - if w * h < min_area: - continue - self.bboxes.append(self._order_points(rect)) - - # pylint: disable=no-self-use - def _min_area_rect(self, cnt): - rect = cv2.minAreaRect(cnt) - w, h = rect[1] - box = cv2.boxPoints(rect) - box = np.int0(box) - return box, w, h - - # pylint: disable=no-self-use - def _order_points(self, rect): - """ (x, y) - Order: TL, TR, BR, BL - """ - tmp = np.zeros_like(rect) - sums = rect.sum(axis=1) - tmp[0] = rect[np.argmin(sums)] - tmp[2] = rect[np.argmax(sums)] - diff = np.diff(rect, axis=1) - tmp[1] = rect[np.argmin(diff)] - tmp[3] = rect[np.argmax(diff)] - return tmp - - def _decode(self): - for point in self.points: - y, x = point - neighbours = self._get_neighbours(x, y) - for n_idx, nx, ny in neighbours: - link_value = self.link_mask[y, x, n_idx] - pixel_cls = self.pixel_mask[ny, nx] - if link_value and pixel_cls: - self._join(point, (ny, nx)) - - self._get_all() - self._mask_to_bboxes() - - -label = 1 -pcd = PixelLinkDecoder() -for detection in detections: - frame = detection['frame_id'] - pcd.decode(detection['frame_height'], detection['frame_width'], detection['detections']) - for box in pcd.bboxes: - box = [[int(b[0]), int(b[1])] for b in box] - results.add_polygon(box, label, frame) diff --git a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md b/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md deleted file mode 100644 index c7bac387865f..000000000000 --- a/utils/open_model_zoo/Retail/object_detection/text/pixel_link_mobilenet_v2/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Pixel Link - -* Model for the Detecting Scene Text vai Instance Segmentation -* Download using the `intel_model_zoo` using `$./downloader.py text-detection-0002` -* See [this Arxiv](https://arxiv.org/abs/1801.01315) link for the technical details diff --git a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py b/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py deleted file mode 100644 index 58a87fa35d1b..000000000000 --- a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/interp.py +++ /dev/null @@ -1,31 +0,0 @@ -import numpy as np -from skimage.measure import approximate_polygon, find_contours - -import cv2 - - -for frame_results in detections: - frame_height = frame_results['frame_height'] - frame_width = frame_results['frame_width'] - frame_number = frame_results['frame_id'] - detection = frame_results['detections'] - detection = detection[0, 0, :, :] - width, height = detection.shape - - for i in range(21): - zero = np.zeros((width,height),dtype=np.uint8) - - f = float(i) - zero = ((detection == f) * 255).astype(np.float32) - zero = cv2.resize(zero, dsize=(frame_width, frame_height), interpolation=cv2.INTER_CUBIC) - - contours = find_contours(zero, 0.8) - - for contour in contours: - contour = np.flip(contour, axis=1) - contour = approximate_polygon(contour, tolerance=2.5) - segmentation = contour.tolist() - if len(segmentation) < 3: - continue - - results.add_polygon(segmentation, i, frame_number) diff --git a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json b/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json deleted file mode 100644 index cbda289d3e2f..000000000000 --- a/utils/open_model_zoo/Transportation/semantic-segmentation-adas/mapping.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "label_map": { - "0": "road", - "1": "sidewalk", - "2": "building", - "3": "wall", - "4": "fence", - "5": "pole", - "6": "traffic light", - "7": "traffic sign", - "8": "vegetation", - "9": "terrain", - "10": "sky", - "11": "person", - "12": "rider", - "13": "car", - "14": "truck", - "15": "bus", - "16": "train", - "17": "motorcycle", - "18": "bicycle", - "19": "ego-vehicle", - "20": "background" - } -} From b04407eaa717df5af51d0621ad1c40a6128a19b2 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 01:07:39 +0300 Subject: [PATCH 37/98] Remove outdated files --- cvat/apps/auto_segmentation/__init__.py | 4 - cvat/apps/auto_segmentation/admin.py | 8 - cvat/apps/auto_segmentation/apps.py | 11 - .../auto_segmentation/migrations/__init__.py | 5 - cvat/apps/auto_segmentation/models.py | 8 - cvat/apps/auto_segmentation/tests.py | 8 - cvat/apps/auto_segmentation/urls.py | 14 - cvat/apps/auto_segmentation/views.py | 310 ------------------ cvat/apps/documentation/installation.md | 2 - 9 files changed, 370 deletions(-) delete mode 100644 cvat/apps/auto_segmentation/__init__.py delete mode 100644 cvat/apps/auto_segmentation/admin.py delete mode 100644 cvat/apps/auto_segmentation/apps.py delete mode 100644 cvat/apps/auto_segmentation/migrations/__init__.py delete mode 100644 cvat/apps/auto_segmentation/models.py delete mode 100644 cvat/apps/auto_segmentation/tests.py delete mode 100644 cvat/apps/auto_segmentation/urls.py delete mode 100644 cvat/apps/auto_segmentation/views.py diff --git a/cvat/apps/auto_segmentation/__init__.py b/cvat/apps/auto_segmentation/__init__.py deleted file mode 100644 index a0fca4cb39ea..000000000000 --- a/cvat/apps/auto_segmentation/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ - -# Copyright (C) 2018-2019 Intel Corporation -# -# SPDX-License-Identifier: MIT diff --git a/cvat/apps/auto_segmentation/admin.py b/cvat/apps/auto_segmentation/admin.py deleted file mode 100644 index 3c40ebdfe118..000000000000 --- a/cvat/apps/auto_segmentation/admin.py +++ /dev/null @@ -1,8 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - - -# Register your models here. - diff --git a/cvat/apps/auto_segmentation/apps.py b/cvat/apps/auto_segmentation/apps.py deleted file mode 100644 index 03322710f457..000000000000 --- a/cvat/apps/auto_segmentation/apps.py +++ /dev/null @@ -1,11 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - - -class AutoSegmentationConfig(AppConfig): - name = 'auto_segmentation' - diff --git a/cvat/apps/auto_segmentation/migrations/__init__.py b/cvat/apps/auto_segmentation/migrations/__init__.py deleted file mode 100644 index d8e62e54b356..000000000000 --- a/cvat/apps/auto_segmentation/migrations/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - diff --git a/cvat/apps/auto_segmentation/models.py b/cvat/apps/auto_segmentation/models.py deleted file mode 100644 index 37401bdd2207..000000000000 --- a/cvat/apps/auto_segmentation/models.py +++ /dev/null @@ -1,8 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - - -# Create your models here. - diff --git a/cvat/apps/auto_segmentation/tests.py b/cvat/apps/auto_segmentation/tests.py deleted file mode 100644 index d20a46ab6a66..000000000000 --- a/cvat/apps/auto_segmentation/tests.py +++ /dev/null @@ -1,8 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - - -# Create your tests here. - diff --git a/cvat/apps/auto_segmentation/urls.py b/cvat/apps/auto_segmentation/urls.py deleted file mode 100644 index f84019be9693..000000000000 --- a/cvat/apps/auto_segmentation/urls.py +++ /dev/null @@ -1,14 +0,0 @@ - -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('create/task/', views.create), - path('check/task/', views.check), - path('cancel/task/', views.cancel), - path('meta/get', views.get_meta_info), -] diff --git a/cvat/apps/auto_segmentation/views.py b/cvat/apps/auto_segmentation/views.py deleted file mode 100644 index 4b15b094f29d..000000000000 --- a/cvat/apps/auto_segmentation/views.py +++ /dev/null @@ -1,310 +0,0 @@ - -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - - -from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest -from rest_framework.decorators import api_view -from rules.contrib.views import permission_required, objectgetter -from cvat.apps.authentication.decorators import login_required -from cvat.apps.dataset_manager.task import put_task_data -from cvat.apps.engine.models import Task as TaskModel -from cvat.apps.engine.serializers import LabeledDataSerializer -from cvat.apps.engine.frame_provider import FrameProvider - -import django_rq -import os -import rq - -import numpy as np - -from cvat.apps.engine.log import slogger - -import sys -import skimage.io -from skimage.measure import find_contours, approximate_polygon - -def run_tensorflow_auto_segmentation(frame_provider, labels_mapping, treshold): - def _convert_to_int(boolean_mask): - return boolean_mask.astype(np.uint8) - - def _convert_to_segmentation(mask): - contours = find_contours(mask, 0.5) - # only one contour exist in our case - contour = contours[0] - contour = np.flip(contour, axis=1) - # Approximate the contour and reduce the number of points - contour = approximate_polygon(contour, tolerance=2.5) - segmentation = contour.ravel().tolist() - return segmentation - - ## INITIALIZATION - - # workarround for tf.placeholder() is not compatible with eager execution - # https://github.com/tensorflow/tensorflow/issues/18165 - import tensorflow as tf - tf.compat.v1.disable_eager_execution() - - # Root directory of the project - ROOT_DIR = os.environ.get('AUTO_SEGMENTATION_PATH') - # Import Mask RCNN - sys.path.append(ROOT_DIR) # To find local version of the library - import mrcnn.model as modellib - - # Import COCO config - sys.path.append(os.path.join(ROOT_DIR, "samples/coco/")) # To find local version - import coco - - # Directory to save logs and trained model - MODEL_DIR = os.path.join(ROOT_DIR, "logs") - - # Local path to trained weights file - COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5") - if COCO_MODEL_PATH is None: - raise OSError('Model path env not found in the system.') - job = rq.get_current_job() - - ## CONFIGURATION - - class InferenceConfig(coco.CocoConfig): - # Set batch size to 1 since we'll be running inference on - # one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU - GPU_COUNT = 1 - IMAGES_PER_GPU = 1 - - # Print config details - config = InferenceConfig() - config.display() - - ## CREATE MODEL AND LOAD TRAINED WEIGHTS - - # Create model object in inference mode. - model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config) - # Load weights trained on MS-COCO - model.load_weights(COCO_MODEL_PATH, by_name=True) - - ## RUN OBJECT DETECTION - result = {} - frames = frame_provider.get_frames(frame_provider.Quality.ORIGINAL) - for image_num, (image_bytes, _) in enumerate(frames): - job.refresh() - if 'cancel' in job.meta: - del job.meta['cancel'] - job.save() - return None - job.meta['progress'] = image_num * 100 / len(frame_provider) - job.save_meta() - - image = skimage.io.imread(image_bytes) - - # for multiple image detection, "batch size" must be equal to number of images - r = model.detect([image], verbose=1) - - r = r[0] - # "r['rois'][index]" gives bounding box around the object - for index, c_id in enumerate(r['class_ids']): - if c_id in labels_mapping.keys(): - if r['scores'][index] >= treshold: - mask = _convert_to_int(r['masks'][:,:,index]) - segmentation = _convert_to_segmentation(mask) - label = labels_mapping[c_id] - if label not in result: - result[label] = [] - result[label].append( - [image_num, segmentation]) - - return result - -def convert_to_cvat_format(data): - result = { - "tracks": [], - "shapes": [], - "tags": [], - "version": 0, - } - - for label in data: - segments = data[label] - for segment in segments: - result['shapes'].append({ - "type": "polygon", - "label_id": label, - "frame": segment[0], - "points": segment[1], - "z_order": 0, - "group": None, - "occluded": False, - "attributes": [], - }) - - return result - -def create_thread(tid, labels_mapping, user): - try: - # If detected object accuracy bigger than threshold it will returend - TRESHOLD = 0.5 - # Init rq job - job = rq.get_current_job() - job.meta['progress'] = 0 - job.save_meta() - # Get job indexes and segment length - db_task = TaskModel.objects.get(pk=tid) - # Get image list - frame_provider = FrameProvider(db_task.data) - - # Run auto segmentation by tf - result = None - slogger.glob.info("auto segmentation with tensorflow framework for task {}".format(tid)) - result = run_tensorflow_auto_segmentation(frame_provider, labels_mapping, TRESHOLD) - - if result is None: - slogger.glob.info('auto segmentation for task {} canceled by user'.format(tid)) - return - - # Modify data format and save - result = convert_to_cvat_format(result) - serializer = LabeledDataSerializer(data = result) - if serializer.is_valid(raise_exception=True): - put_task_data(tid, result) - slogger.glob.info('auto segmentation for task {} done'.format(tid)) - except Exception as ex: - try: - slogger.task[tid].exception('exception was occured during auto segmentation of the task', exc_info=True) - except Exception: - slogger.glob.exception('exception was occured during auto segmentation of the task {}'.format(tid), exc_info=True) - raise ex - -@api_view(['POST']) -@login_required -def get_meta_info(request): - try: - queue = django_rq.get_queue('low') - tids = request.data - result = {} - for tid in tids: - job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) - if job is not None: - result[tid] = { - "active": job.is_queued or job.is_started, - "success": not job.is_failed - } - - return JsonResponse(result) - except Exception as ex: - slogger.glob.exception('exception was occured during tf meta request', exc_info=True) - return HttpResponseBadRequest(str(ex)) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def create(request, tid): - slogger.glob.info('auto segmentation create request for task {}'.format(tid)) - try: - db_task = TaskModel.objects.get(pk=tid) - queue = django_rq.get_queue('low') - job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) - if job is not None and (job.is_started or job.is_queued): - raise Exception("The process is already running") - - db_labels = db_task.label_set.prefetch_related('attributespec_set').all() - db_labels = {db_label.id:db_label.name for db_label in db_labels} - - # COCO Labels - auto_segmentation_labels = { "BG": 0, - "person": 1, "bicycle": 2, "car": 3, "motorcycle": 4, "airplane": 5, - "bus": 6, "train": 7, "truck": 8, "boat": 9, "traffic_light": 10, - "fire_hydrant": 11, "stop_sign": 12, "parking_meter": 13, "bench": 14, - "bird": 15, "cat": 16, "dog": 17, "horse": 18, "sheep": 19, "cow": 20, - "elephant": 21, "bear": 22, "zebra": 23, "giraffe": 24, "backpack": 25, - "umbrella": 26, "handbag": 27, "tie": 28, "suitcase": 29, "frisbee": 30, - "skis": 31, "snowboard": 32, "sports_ball": 33, "kite": 34, "baseball_bat": 35, - "baseball_glove": 36, "skateboard": 37, "surfboard": 38, "tennis_racket": 39, - "bottle": 40, "wine_glass": 41, "cup": 42, "fork": 43, "knife": 44, "spoon": 45, - "bowl": 46, "banana": 47, "apple": 48, "sandwich": 49, "orange": 50, "broccoli": 51, - "carrot": 52, "hot_dog": 53, "pizza": 54, "donut": 55, "cake": 56, "chair": 57, - "couch": 58, "potted_plant": 59, "bed": 60, "dining_table": 61, "toilet": 62, - "tv": 63, "laptop": 64, "mouse": 65, "remote": 66, "keyboard": 67, "cell_phone": 68, - "microwave": 69, "oven": 70, "toaster": 71, "sink": 72, "refrigerator": 73, - "book": 74, "clock": 75, "vase": 76, "scissors": 77, "teddy_bear": 78, "hair_drier": 79, - "toothbrush": 80 - } - - labels_mapping = {} - for key, labels in db_labels.items(): - if labels in auto_segmentation_labels.keys(): - labels_mapping[auto_segmentation_labels[labels]] = key - - if not len(labels_mapping.values()): - raise Exception('No labels found for auto segmentation') - - # Run auto segmentation job - queue.enqueue_call(func=create_thread, - args=(tid, labels_mapping, request.user), - job_id='auto_segmentation.create/{}'.format(tid), - timeout=604800) # 7 days - - slogger.task[tid].info('tensorflow segmentation job enqueued with labels {}'.format(labels_mapping)) - - except Exception as ex: - try: - slogger.task[tid].exception("exception was occured during tensorflow segmentation request", exc_info=True) - except Exception: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() - -@login_required -@permission_required(perm=['engine.task.access'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def check(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) - if job is not None and 'cancel' in job.meta: - return JsonResponse({'status': 'finished'}) - data = {} - if job is None: - data['status'] = 'unknown' - elif job.is_queued: - data['status'] = 'queued' - elif job.is_started: - data['status'] = 'started' - data['progress'] = job.meta['progress'] - elif job.is_finished: - data['status'] = 'finished' - job.delete() - else: - data['status'] = 'failed' - data['stderr'] = job.exc_info - job.delete() - - except Exception: - data['status'] = 'unknown' - - return JsonResponse(data) - - -@login_required -@permission_required(perm=['engine.task.change'], - fn=objectgetter(TaskModel, 'tid'), raise_exception=True) -def cancel(request, tid): - try: - queue = django_rq.get_queue('low') - job = queue.fetch_job('auto_segmentation.create/{}'.format(tid)) - if job is None or job.is_finished or job.is_failed: - raise Exception('Task is not being segmented currently') - elif 'cancel' not in job.meta: - job.meta['cancel'] = True - job.save() - - except Exception as ex: - try: - slogger.task[tid].exception("cannot cancel tensorflow segmentation for task #{}".format(tid), exc_info=True) - except Exception: - pass - return HttpResponseBadRequest(str(ex)) - - return HttpResponse() diff --git a/cvat/apps/documentation/installation.md b/cvat/apps/documentation/installation.md index d9083b84229d..7bcb49ac1546 100644 --- a/cvat/apps/documentation/installation.md +++ b/cvat/apps/documentation/installation.md @@ -243,8 +243,6 @@ server. Proxy is an advanced topic and it is not covered by the guide. ### Additional components - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) -- [Semi-automatic segmentation with Deep Extreme Cut](/cvat/apps/dextr_segmentation/README.md) -- [Auto segmentation: Keras+Tensorflow Mask R-CNN Segmentation](/components/auto_segmentation/README.md) ```bash # Build and run containers with Analytics component support: From f518ba8edcbd2bcc2548c7dd37c07f1c2cc5329e Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 01:12:12 +0300 Subject: [PATCH 38/98] Remove outdated code --- CONTRIBUTING.md | 11 ----------- Dockerfile | 8 -------- cvat/apps/documentation/faq.md | 16 ++++++---------- cvat/settings/base.py | 4 ---- cvat/urls.py | 4 ---- docker-compose.yml | 2 -- 6 files changed, 6 insertions(+), 39 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4e78dddf8171..d85c2f071093 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,17 +117,6 @@ to changes in ``.env/bin/activate`` file are active. export REID_MODEL_DIR="/path/to/dir" # dir must contain .xml and .bin files ``` -### Tensorflow Mask RCNN -- Download Mask RCNN model, and save it somewhere: -```sh -curl https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5 -o mask_rcnn_coco.h5 -``` -- Add next lines to ``.env/bin/activate``: -```sh - export AUTO_SEGMENTATION="yes" - export AUTO_SEGMENTATION_PATH="/path/to/dir" # dir must contain mask_rcnn_coco.h5 file -``` - ## JavaScript/Typescript coding style We use the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) for JavaScript code with a diff --git a/Dockerfile b/Dockerfile index fee5c493fb39..e5d389e6547d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -88,14 +88,6 @@ RUN if [ "$OPENVINO_TOOLKIT" = "yes" ]; then \ curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.bin -o reid/reid.bin; \ fi -# Auto segmentation support. by Mohammad -ARG AUTO_SEGMENTATION -ENV AUTO_SEGMENTATION=${AUTO_SEGMENTATION} -ENV AUTO_SEGMENTATION_PATH=${HOME}/Mask_RCNN -RUN if [ "$AUTO_SEGMENTATION" = "yes" ]; then \ - bash -i /tmp/components/auto_segmentation/install.sh; \ - fi - # Install and initialize CVAT, copy all necessary files COPY cvat/requirements/ /tmp/requirements/ COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ diff --git a/cvat/apps/documentation/faq.md b/cvat/apps/documentation/faq.md index 3bdea808992b..df20a7527198 100644 --- a/cvat/apps/documentation/faq.md +++ b/cvat/apps/documentation/faq.md @@ -1,10 +1,10 @@ # Frequently asked questions -- [How to update CVAT](#how-to-update-cvat) -- [Kibana app works, but no logs are displayed](#kibana-app-works-but-no-logs-are-displayed) -- [How to change default CVAT hostname or port](#how-to-change-default-cvat-hostname-or-port) -- [How to configure connected share folder on Windows](#how-to-configure-connected-share-folder-on-windows) -- [How to make unassigned tasks not visible to all users](#how-to-make-unassigned-tasks-not-visible-to-all-users) -- [Can Nvidia GPU be used to run inference with my own model](#can-nvidia-gpu-be-used-to-run-inference-with-my-own-model) +- [Frequently asked questions](#frequently-asked-questions) + - [How to update CVAT](#how-to-update-cvat) + - [Kibana app works, but no logs are displayed](#kibana-app-works-but-no-logs-are-displayed) + - [How to change default CVAT hostname or port](#how-to-change-default-cvat-hostname-or-port) + - [How to configure connected share folder on Windows](#how-to-configure-connected-share-folder-on-windows) + - [How to make unassigned tasks not visible to all users](#how-to-make-unassigned-tasks-not-visible-to-all-users) ## How to update CVAT Before upgrading, please follow the official docker @@ -75,7 +75,3 @@ volumes: ## How to make unassigned tasks not visible to all users Set [reduce_task_visibility](../../settings/base.py#L424) variable to `True`. -## Can Nvidia GPU be used to run inference with my own model -Nvidia GPU can be used to accelerate inference of [tf_annotation](../../../components/tf_annotation/README.md) and [auto_segmentation](../../../components/auto_segmentation/README.md) models. - -OpenVino doesn't support Nvidia cards, so you can run your own models only on CPU. diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 514ed32d8a5c..0d7b8c2148e4 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -167,10 +167,6 @@ def generate_ssh_keys(): if os.getenv('DJANGO_LOG_VIEWER_HOST'): INSTALLED_APPS += ['cvat.apps.log_viewer'] -# new feature by Mohammad -if 'yes' == os.environ.get('AUTO_SEGMENTATION', 'no'): - INSTALLED_APPS += ['cvat.apps.auto_segmentation'] - MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/cvat/urls.py b/cvat/urls.py index 376ff7e55a50..ee34c755f241 100644 --- a/cvat/urls.py +++ b/cvat/urls.py @@ -45,7 +45,3 @@ if apps.is_installed('silk'): urlpatterns.append(path('profiler/', include('silk.urls'))) - -# new feature by Mohammad -if apps.is_installed('cvat.apps.auto_segmentation'): - urlpatterns.append(path('tensorflow/segmentation/', include('cvat.apps.auto_segmentation.urls'))) diff --git a/docker-compose.yml b/docker-compose.yml index bc42d73d8286..6f0c2359fe5c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,11 +44,9 @@ services: https_proxy: no_proxy: socks_proxy: - AUTO_SEGMENTATION: "no" USER: "django" DJANGO_CONFIGURATION: "production" TZ: "Etc/UTC" - OPENVINO_TOOLKIT: "no" CLAM_AV: "no" environment: DJANGO_MODWSGI_EXTRA_ARGS: "" From d0b5cfb72b6f3d6fb5228a5136c89cc3a85adc08 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sat, 20 Jun 2020 09:48:29 +0300 Subject: [PATCH 39/98] Delete reid app and add draft of serverless function for reid. --- .gitattributes | 1 - .gitignore | 1 - CONTRIBUTING.md | 19 -- Dockerfile | 11 - cvat/apps/reid/README.md | 22 -- cvat/apps/reid/__init__.py | 9 - cvat/apps/reid/apps.py | 8 - cvat/apps/reid/reid.py | 227 ------------------ cvat/apps/reid/static/reid/js/enginePlugin.js | 174 -------------- cvat/apps/reid/urls.py | 13 - cvat/apps/reid/views.py | 99 -------- .../nuclio/function.yaml | 51 ++++ .../nuclio/inference_engine.py | 52 ++++ .../nuclio/main.py | 28 +++ .../nuclio/model_loader.py | 69 ++++++ .../nuclio/python3 | 6 + 16 files changed, 206 insertions(+), 584 deletions(-) delete mode 100644 cvat/apps/reid/README.md delete mode 100644 cvat/apps/reid/__init__.py delete mode 100644 cvat/apps/reid/apps.py delete mode 100644 cvat/apps/reid/reid.py delete mode 100644 cvat/apps/reid/static/reid/js/enginePlugin.js delete mode 100644 cvat/apps/reid/urls.py delete mode 100644 cvat/apps/reid/views.py create mode 100644 serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/function.yaml create mode 100644 serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/inference_engine.py create mode 100644 serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/main.py create mode 100644 serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/model_loader.py create mode 100755 serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/python3 diff --git a/.gitattributes b/.gitattributes index f41e91d85c22..7c0b591b072c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,7 +15,6 @@ LICENSE text *.conf text *.mimetypes text *.sh text eol=lf -components/openvino/eula.cfg text eol=lf *.avi binary *.bmp binary diff --git a/.gitignore b/.gitignore index c25a121364a1..cbe91f69a9c3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ /.env /keys /logs -/components/openvino/*.tgz /profiles /ssh/* !/ssh/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d85c2f071093..5d2acd90d5b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,25 +86,6 @@ You have done! Now it is possible to insert breakpoints and debug server and cli ## Setup additional components in development environment -### Automatic annotation -- Install OpenVINO on your host machine according to instructions from -[OpenVINO website](https://docs.openvinotoolkit.org/latest/index.html) - -- Add some environment variables (copy code below to the end of ``.env/bin/activate`` file): -```sh - source /opt/intel/openvino/bin/setupvars.sh - - export OPENVINO_TOOLKIT="yes" - export IE_PLUGINS_PATH="/opt/intel/openvino/deployment_tools/inference_engine/lib/intel64" - export OpenCV_DIR="/usr/local/lib/cmake/opencv4" - export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/opt/intel/openvino/inference_engine/lib/intel64" -``` - -Notice 1: be sure that these paths actually exist. Some of them can differ in different OpenVINO versions. - -Notice 2: you need to deactivate, activate again and restart vs code -to changes in ``.env/bin/activate`` file are active. - ### ReID algorithm - Perform all steps in the automatic annotation section - Download ReID model and save it somewhere: diff --git a/Dockerfile b/Dockerfile index e5d389e6547d..4682c7f9aca9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -77,17 +77,6 @@ RUN adduser --shell /bin/bash --disabled-password --gecos "" ${USER} && \ COPY components /tmp/components -# OpenVINO toolkit support -ARG OPENVINO_TOOLKIT -ENV OPENVINO_TOOLKIT=${OPENVINO_TOOLKIT} -ENV REID_MODEL_DIR=${HOME}/reid -RUN if [ "$OPENVINO_TOOLKIT" = "yes" ]; then \ - /tmp/components/openvino/install.sh && \ - mkdir ${REID_MODEL_DIR} && \ - curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.xml -o reid/reid.xml && \ - curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.bin -o reid/reid.bin; \ - fi - # Install and initialize CVAT, copy all necessary files COPY cvat/requirements/ /tmp/requirements/ COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ diff --git a/cvat/apps/reid/README.md b/cvat/apps/reid/README.md deleted file mode 100644 index 8cf29c503781..000000000000 --- a/cvat/apps/reid/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Re-Identification Application - -## About the application - -The ReID application uses deep learning model to perform an automatic bbox merging between neighbor frames. -You can use "Merge" and "Split" functionality to edit automatically generated annotation. - -## Installation - -This application will be installed automatically with the [OpenVINO](https://github.com/opencv/cvat/blob/develop/components/openvino/README.md) component. - -## Running - -For starting the ReID merge process: - -- Open an annotation job -- Open the menu -- Click the "Run ReID Merge" button -- Click the "Submit" button. Also here you can experiment with values of model threshold or maximum distance. - - Model threshold is maximum cosine distance between objects embeddings. - - Maximum distance defines a maximum radius that an object can diverge between neightbor frames. -- The process will be run. You can cancel it in the menu. diff --git a/cvat/apps/reid/__init__.py b/cvat/apps/reid/__init__.py deleted file mode 100644 index 4a9759cc68f5..000000000000 --- a/cvat/apps/reid/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from cvat.settings.base import JS_3RDPARTY - -default_app_config = 'cvat.apps.reid.apps.ReidConfig' - -JS_3RDPARTY['engine'] = JS_3RDPARTY.get('engine', []) + ['reid/js/enginePlugin.js'] diff --git a/cvat/apps/reid/apps.py b/cvat/apps/reid/apps.py deleted file mode 100644 index f6aa66e1a5f9..000000000000 --- a/cvat/apps/reid/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.apps import AppConfig - -class ReidConfig(AppConfig): - name = 'cvat.apps.reid' diff --git a/cvat/apps/reid/reid.py b/cvat/apps/reid/reid.py deleted file mode 100644 index bf79bac9a238..000000000000 --- a/cvat/apps/reid/reid.py +++ /dev/null @@ -1,227 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import os -import rq -import cv2 -import math -import numpy -import itertools - -from openvino.inference_engine import IENetwork, IEPlugin -from scipy.optimize import linear_sum_assignment -from scipy.spatial.distance import euclidean, cosine - -from cvat.apps.engine.models import Job -from cvat.apps.engine.frame_provider import FrameProvider - - -class ReID: - __threshold = None - __max_distance = None - __frame_urls = None - __frame_boxes = None - __stop_frame = None - __plugin = None - __executable_network = None - __input_blob_name = None - __output_blob_name = None - __input_height = None - __input_width = None - - - def __init__(self, jid, data): - self.__threshold = data["threshold"] - self.__max_distance = data["maxDistance"] - - self.__frame_boxes = {} - - db_job = Job.objects.select_related('segment__task').get(pk = jid) - db_segment = db_job.segment - db_task = db_segment.task - self.__frame_iter = itertools.islice( - FrameProvider(db_task.data).get_frames(FrameProvider.Quality.ORIGINAL), - db_segment.start_frame, - db_segment.stop_frame + 1, - ) - - self.__stop_frame = db_segment.stop_frame - for frame in range(db_segment.start_frame, db_segment.stop_frame + 1): - self.__frame_boxes[frame] = [box for box in data["boxes"] if box["frame"] == frame] - - IE_PLUGINS_PATH = os.getenv('IE_PLUGINS_PATH', None) - REID_MODEL_DIR = os.getenv('REID_MODEL_DIR', None) - - if not IE_PLUGINS_PATH: - raise Exception("Environment variable 'IE_PLUGINS_PATH' isn't defined") - if not REID_MODEL_DIR: - raise Exception("Environment variable 'REID_MODEL_DIR' isn't defined") - - REID_XML = os.path.join(REID_MODEL_DIR, "reid.xml") - REID_BIN = os.path.join(REID_MODEL_DIR, "reid.bin") - - self.__plugin = IEPlugin(device="CPU", plugin_dirs=[IE_PLUGINS_PATH]) - network = IENetwork.from_ir(model=REID_XML, weights=REID_BIN) - self.__input_blob_name = next(iter(network.inputs)) - self.__output_blob_name = next(iter(network.outputs)) - self.__input_height, self.__input_width = network.inputs[self.__input_blob_name].shape[-2:] - self.__executable_network = self.__plugin.load(network=network) - del network - - - def __del__(self): - if self.__executable_network: - del self.__executable_network - self.__executable_network = None - - if self.__plugin: - del self.__plugin - self.__plugin = None - - - def __boxes_are_compatible(self, cur_box, next_box): - cur_c_x = (cur_box["points"][0] + cur_box["points"][2]) / 2 - cur_c_y = (cur_box["points"][1] + cur_box["points"][3]) / 2 - next_c_x = (next_box["points"][0] + next_box["points"][2]) / 2 - next_c_y = (next_box["points"][1] + next_box["points"][3]) / 2 - compatible_distance = euclidean([cur_c_x, cur_c_y], [next_c_x, next_c_y]) <= self.__max_distance - compatible_label = cur_box["label_id"] == next_box["label_id"] - return compatible_distance and compatible_label and "path_id" not in next_box - - - def __compute_difference(self, image_1, image_2): - image_1 = cv2.resize(image_1, (self.__input_width, self.__input_height)).transpose((2,0,1)) - image_2 = cv2.resize(image_2, (self.__input_width, self.__input_height)).transpose((2,0,1)) - - input_1 = { - self.__input_blob_name: image_1[numpy.newaxis, ...] - } - - input_2 = { - self.__input_blob_name: image_2[numpy.newaxis, ...] - } - - embedding_1 = self.__executable_network.infer(inputs = input_1)[self.__output_blob_name] - embedding_2 = self.__executable_network.infer(inputs = input_2)[self.__output_blob_name] - - embedding_1 = embedding_1.reshape(embedding_1.size) - embedding_2 = embedding_2.reshape(embedding_2.size) - - return cosine(embedding_1, embedding_2) - - - def __compute_difference_matrix(self, cur_boxes, next_boxes, cur_image, next_image): - def _int(number, upper): - return math.floor(numpy.clip(number, 0, upper - 1)) - - default_mat_value = 1000.0 - - matrix = numpy.full([len(cur_boxes), len(next_boxes)], default_mat_value, dtype=float) - for row, cur_box in enumerate(cur_boxes): - cur_width = cur_image.shape[1] - cur_height = cur_image.shape[0] - cur_xtl, cur_xbr, cur_ytl, cur_ybr = ( - _int(cur_box["points"][0], cur_width), _int(cur_box["points"][2], cur_width), - _int(cur_box["points"][1], cur_height), _int(cur_box["points"][3], cur_height) - ) - - for col, next_box in enumerate(next_boxes): - next_box = next_boxes[col] - next_width = next_image.shape[1] - next_height = next_image.shape[0] - next_xtl, next_xbr, next_ytl, next_ybr = ( - _int(next_box["points"][0], next_width), _int(next_box["points"][2], next_width), - _int(next_box["points"][1], next_height), _int(next_box["points"][3], next_height) - ) - - if not self.__boxes_are_compatible(cur_box, next_box): - continue - - crop_1 = cur_image[cur_ytl:cur_ybr, cur_xtl:cur_xbr] - crop_2 = next_image[next_ytl:next_ybr, next_xtl:next_xbr] - matrix[row][col] = self.__compute_difference(crop_1, crop_2) - - return matrix - - - def __apply_matching(self): - frames = sorted(list(self.__frame_boxes.keys())) - job = rq.get_current_job() - box_tracks = {} - - next_image = cv2.imdecode(numpy.fromstring((next(self.__frame_iter)[0]).read(), numpy.uint8), cv2.IMREAD_COLOR) - for idx, (cur_frame, next_frame) in enumerate(list(zip(frames[:-1], frames[1:]))): - job.refresh() - if "cancel" in job.meta: - return None - - job.meta["progress"] = idx * 100.0 / len(frames) - job.save_meta() - - cur_boxes = self.__frame_boxes[cur_frame] - next_boxes = self.__frame_boxes[next_frame] - - for box in cur_boxes: - if "path_id" not in box: - path_id = len(box_tracks) - box_tracks[path_id] = [box] - box["path_id"] = path_id - - if not (len(cur_boxes) and len(next_boxes)): - continue - - cur_image = next_image - next_image = cv2.imdecode(numpy.fromstring((next(self.__frame_iter)[0]).read(), numpy.uint8), cv2.IMREAD_COLOR) - difference_matrix = self.__compute_difference_matrix(cur_boxes, next_boxes, cur_image, next_image) - cur_idxs, next_idxs = linear_sum_assignment(difference_matrix) - for idx, cur_idx in enumerate(cur_idxs): - if (difference_matrix[cur_idx][next_idxs[idx]]) <= self.__threshold: - cur_box = cur_boxes[cur_idx] - next_box = next_boxes[next_idxs[idx]] - next_box["path_id"] = cur_box["path_id"] - box_tracks[cur_box["path_id"]].append(next_box) - - for box in self.__frame_boxes[frames[-1]]: - if "path_id" not in box: - path_id = len(box_tracks) - box["path_id"] = path_id - box_tracks[path_id] = [box] - - return box_tracks - - - def run(self): - box_tracks = self.__apply_matching() - output = [] - - # ReID process has been canceled - if box_tracks is None: - return - - for path_id in box_tracks: - output.append({ - "label_id": box_tracks[path_id][0]["label_id"], - "group": None, - "attributes": [], - "frame": box_tracks[path_id][0]["frame"], - "shapes": box_tracks[path_id] - }) - - for box in output[-1]["shapes"]: - if "id" in box: - del box["id"] - del box["path_id"] - del box["group"] - del box["label_id"] - box["outside"] = False - box["attributes"] = [] - - for path in output: - if path["shapes"][-1]["frame"] != self.__stop_frame: - copy = path["shapes"][-1].copy() - copy["outside"] = True - copy["frame"] += 1 - path["shapes"].append(copy) - - return output diff --git a/cvat/apps/reid/static/reid/js/enginePlugin.js b/cvat/apps/reid/static/reid/js/enginePlugin.js deleted file mode 100644 index 28fabb82319c..000000000000 --- a/cvat/apps/reid/static/reid/js/enginePlugin.js +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2018 Intel Corporation - * - * SPDX-License-Identifier: MIT - */ - -/* global showMessage userConfirm */ - - -document.addEventListener('DOMContentLoaded', () => { - async function run(overlay, cancelButton, thresholdInput, distanceInput) { - const collection = window.cvat.data.get(); - const data = { - threshold: +thresholdInput.prop('value'), - maxDistance: +distanceInput.prop('value'), - boxes: collection.shapes.filter(el => el.type === 'rectangle'), - }; - - overlay.removeClass('hidden'); - cancelButton.prop('disabled', true); - - async function checkCallback() { - let jobData = null; - try { - jobData = await $.get(`/reid/check/${window.cvat.job.id}`); - } catch (errorData) { - overlay.addClass('hidden'); - const message = `Can not check ReID merge. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - } - - if (jobData.progress) { - cancelButton.text(`Cancel ReID Merge (${jobData.progress.toString().slice(0, 4)}%)`); - } - - if (['queued', 'started'].includes(jobData.status)) { - setTimeout(checkCallback, 1000); - } else { - overlay.addClass('hidden'); - - if (jobData.status === 'finished') { - if (jobData.result) { - const result = JSON.parse(jobData.result); - collection.shapes = collection.shapes - .filter(el => el.type !== 'rectangle'); - collection.tracks = collection.tracks - .concat(result); - - window.cvat.data.clear(); - window.cvat.data.set(collection); - - showMessage('ReID merge has done.'); - } else { - showMessage('ReID merge been canceled.'); - } - } else if (jobData.status === 'failed') { - const message = `ReID merge has fallen. Error: '${jobData.stderr}'`; - showMessage(message); - } else { - let message = `Check request returned "${jobData.status}" status.`; - if (jobData.stderr) { - message += ` Error: ${jobData.stderr}`; - } - showMessage(message); - } - } - } - - try { - await $.ajax({ - url: `/reid/start/job/${window.cvat.job.id}`, - type: 'POST', - data: JSON.stringify(data), - contentType: 'application/json', - }); - - setTimeout(checkCallback, 1000); - } catch (errorData) { - overlay.addClass('hidden'); - const message = `Can not start ReID merge. Code: ${errorData.status}. ` - + `Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - } finally { - cancelButton.prop('disabled', false); - } - } - - async function cancel(overlay, cancelButton) { - cancelButton.prop('disabled', true); - try { - await $.get(`/reid/cancel/${window.cvat.job.id}`); - overlay.addClass('hidden'); - cancelButton.text('Cancel ReID Merge (0%)'); - } catch (errorData) { - const message = `Can not cancel ReID process. Code: ${errorData.status}. Message: ${errorData.responseText || errorData.statusText}`; - showMessage(message); - } finally { - cancelButton.prop('disabled', false); - } - } - - const buttonsUI = $('#engineMenuButtons'); - const reidWindowId = 'reidSubmitWindow'; - const reidThresholdValueId = 'reidThresholdValue'; - const reidDistanceValueId = 'reidDistanceValue'; - const reidCancelMergeId = 'reidCancelMerge'; - const reidSubmitMergeId = 'reidSubmitMerge'; - const reidCancelButtonId = 'reidCancelReID'; - const reidOverlay = 'reidOverlay'; - - $('').on('click', () => { - $('#annotationMenu').addClass('hidden'); - $(`#${reidWindowId}`).removeClass('hidden'); - }).addClass('menuButton semiBold h2').prependTo(buttonsUI); - - $(` - - `).appendTo('body'); - - $(` - - `).appendTo('body'); - - $(`#${reidCancelMergeId}`).on('click', () => { - $(`#${reidWindowId}`).addClass('hidden'); - }); - - $(`#${reidCancelButtonId}`).on('click', () => { - userConfirm('ReID process will be canceld. Are you sure?', () => { - cancel($(`#${reidOverlay}`), $(`#${reidCancelButtonId}`)); - }); - }); - - $(`#${reidSubmitMergeId}`).on('click', () => { - $(`#${reidWindowId}`).addClass('hidden'); - run($(`#${reidOverlay}`), $(`#${reidCancelButtonId}`), - $(`#${reidThresholdValueId}`), $(`#${reidDistanceValueId}`)) - .catch((error) => { - setTimeout(() => { - throw error; - }); - }); - }); -}); diff --git a/cvat/apps/reid/urls.py b/cvat/apps/reid/urls.py deleted file mode 100644 index 431b11192ec7..000000000000 --- a/cvat/apps/reid/urls.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (C) 2018-2020 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.urls import path -from . import views - -urlpatterns = [ - path('start/job/', views.start), - path('cancel/', views.cancel), - path('check/', views.check), - path('enabled', views.enabled), -] diff --git a/cvat/apps/reid/views.py b/cvat/apps/reid/views.py deleted file mode 100644 index 100151ee9d1b..000000000000 --- a/cvat/apps/reid/views.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright (C) 2018 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse -from cvat.apps.authentication.decorators import login_required -from rules.contrib.views import permission_required, objectgetter - -from cvat.apps.engine.models import Job -from cvat.apps.reid.reid import ReID - -import django_rq -import json -import rq - - -def _create_thread(jid, data): - job = rq.get_current_job() - reid_obj = ReID(jid, data) - job.meta["result"] = json.dumps(reid_obj.run()) - job.save_meta() - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, 'jid'), raise_exception=True) -def start(request, jid): - try: - data = json.loads(request.body.decode('utf-8')) - queue = django_rq.get_queue("low") - job_id = "reid.create.{}".format(jid) - job = queue.fetch_job(job_id) - if job is not None and (job.is_started or job.is_queued): - raise Exception('ReID process has been already started') - queue.enqueue_call(func=_create_thread, args=(jid, data), job_id=job_id, timeout=7200) - job = queue.fetch_job(job_id) - job.meta = {} - job.save_meta() - except Exception as e: - return HttpResponseBadRequest(str(e)) - - return HttpResponse() - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, 'jid'), raise_exception=True) -def check(request, jid): - try: - queue = django_rq.get_queue("low") - rq_id = "reid.create.{}".format(jid) - job = queue.fetch_job(rq_id) - if job is not None and "cancel" in job.meta: - return JsonResponse({"status": "finished"}) - data = {} - if job is None: - data["status"] = "unknown" - elif job.is_queued: - data["status"] = "queued" - elif job.is_started: - data["status"] = "started" - if "progress" in job.meta: - data["progress"] = job.meta["progress"] - elif job.is_finished: - data["status"] = "finished" - data["result"] = job.meta["result"] - job.delete() - else: - data["status"] = "failed" - data["stderr"] = job.exc_info - job.delete() - - except Exception as ex: - data["stderr"] = str(ex) - data["status"] = "unknown" - - return JsonResponse(data) - - -@login_required -@permission_required(perm=["engine.job.change"], - fn=objectgetter(Job, 'jid'), raise_exception=True) -def cancel(request, jid): - try: - queue = django_rq.get_queue("low") - rq_id = "reid.create.{}".format(jid) - job = queue.fetch_job(rq_id) - if job is None or job.is_finished or job.is_failed: - raise Exception("Task is not being annotated currently") - elif "cancel" not in job.meta: - job.meta["cancel"] = True - job.save_meta() - except Exception as e: - return HttpResponseBadRequest(str(e)) - - return HttpResponse() - -def enabled(request): - return HttpResponse() diff --git a/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/function.yaml b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/function.yaml new file mode 100644 index 000000000000..2ac60494bf72 --- /dev/null +++ b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/function.yaml @@ -0,0 +1,51 @@ +metadata: + name: omz.intel.person-reidentification-retail + namespace: cvat + annotations: + type: reid + framework: openvino + spec: + +spec: + description: Person reidentification model for a general scenario + runtime: "python:3.6" + handler: main:handler + eventTimeout: 30s + env: + - name: NUCLIO_PYTHON_EXE_PATH + value: /opt/nuclio/python3 + + build: + image: cvat/open_model_zoo/intel/person-reidentification-retail + baseImage: openvino/ubuntu18_dev:2020.2 + + directives: + preCopy: + - kind: USER + value: root + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: ln -s /usr/bin/pip3 /usr/bin/pip + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/downloader.py --name person-reidentification-retail-300 -o /opt/nuclio/open_model_zoo + - kind: RUN + value: /opt/intel/openvino/deployment_tools/open_model_zoo/tools/downloader/converter.py --name person-reidentification-retail-300 --precisions FP32 -d /opt/nuclio/open_model_zoo -o /opt/nuclio/open_model_zoo + + postCopy: + - kind: USER + value: openvino + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: "http" + workerAvailabilityTimeoutMilliseconds: 10000 + + platform: + attributes: + network: "cvat_default" + + restartPolicy: + name: always + maximumRetryCount: 3 diff --git a/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/inference_engine.py b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/inference_engine.py new file mode 100644 index 000000000000..226af0b1416e --- /dev/null +++ b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/inference_engine.py @@ -0,0 +1,52 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from openvino.inference_engine import IENetwork, IEPlugin, IECore, get_version + +import subprocess +import os +import platform + +_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None) + +def _check_instruction(instruction): + return instruction == str.strip( + subprocess.check_output( + 'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True + ).decode('utf-8') + ) + + +def make_plugin_or_core(): + version = get_version() + use_core_openvino = False + try: + major, minor, _ = [int(x) for x in version.split('.')] + if major >= 2 and minor >= 1: + use_core_openvino = True + except Exception: + pass + + if use_core_openvino: + ie = IECore() + return ie + + if _IE_PLUGINS_PATH is None: + raise OSError('Inference engine plugin path env not found in the system.') + + plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH]) + if (_check_instruction('avx2')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so')) + elif (_check_instruction('sse4')): + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so')) + elif platform.system() == 'Darwin': + plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension.dylib')) + else: + raise Exception('Inference engine requires a support of avx2 or sse4.') + + return plugin + + +def make_network(model, weights): + return IENetwork(model = model, weights = weights) diff --git a/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/main.py b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/main.py new file mode 100644 index 000000000000..6610472c3928 --- /dev/null +++ b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/main.py @@ -0,0 +1,28 @@ +import json +import base64 +from PIL import Image +import io +from model_loader import ModelLoader +import numpy as np + +def init_context(context): + context.logger.info("Init context... 0%") + model_xml = "/opt/nuclio/open_model_zoo/intel/person-reidentification-retail-300/FP32/person-reidentification-retail-300.xml" + model_bin = "/opt/nuclio/open_model_zoo/intel/person-reidentification-retail-300/FP32/person-reidentification-retail-300.bin" + model_handler = ModelLoader(model_xml, model_bin) + setattr(context.user_data, 'model_handler', model_handler) + context.logger.info("Init context...100%") + +def handler(context, event): + context.logger.info("Run faster_rcnn_inception_v2_coco model") + data = event.body + buf = io.BytesIO(base64.b64decode(data["image"].encode('utf-8'))) + threshold = float(data.get("threshold", 0.5)) + image = Image.open(buf) + + output_layer = context.user_data.model_handler.infer(np.array(image)) + + results = [] + + return context.Response(body=json.dumps(results), headers={}, + content_type='application/json', status_code=200) diff --git a/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/model_loader.py b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/model_loader.py new file mode 100644 index 000000000000..c70baf6ea03b --- /dev/null +++ b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/model_loader.py @@ -0,0 +1,69 @@ + +# Copyright (C) 2018-2019 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import cv2 +import numpy as np + +from inference_engine import make_plugin_or_core, make_network + +class ModelLoader: + def __init__(self, model, weights): + self._model = model + self._weights = weights + + core_or_plugin = make_plugin_or_core() + network = make_network(self._model, self._weights) + + if getattr(core_or_plugin, 'get_supported_layers', False): + supported_layers = core_or_plugin.get_supported_layers(network) + not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers] + if len(not_supported_layers) != 0: + raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}". + format(core_or_plugin.device, ", ".join(not_supported_layers))) + + iter_inputs = iter(network.inputs) + self._input_blob_name = next(iter_inputs) + self._input_info_name = '' + self._output_blob_name = next(iter(network.outputs)) + + self._require_image_info = False + + info_names = ('image_info', 'im_info') + + # NOTE: handeling for the inclusion of `image_info` in OpenVino2019 + if any(s in network.inputs for s in info_names): + self._require_image_info = True + self._input_info_name = set(network.inputs).intersection(info_names) + self._input_info_name = self._input_info_name.pop() + if self._input_blob_name in info_names: + self._input_blob_name = next(iter_inputs) + + if getattr(core_or_plugin, 'load_network', False): + self._net = core_or_plugin.load_network(network, + "CPU", + num_requests=2) + else: + self._net = core_or_plugin.load(network=network, num_requests=2) + input_type = network.inputs[self._input_blob_name] + self._input_layout = input_type if isinstance(input_type, list) else input_type.shape + + def infer(self, image): + _, _, h, w = self._input_layout + in_frame = image if image.shape[:-1] == (h, w) else cv2.resize(image, (w, h)) + in_frame = in_frame.transpose((2, 0, 1)) # Change data layout from HWC to CHW + inputs = {self._input_blob_name: in_frame} + if self._require_image_info: + info = np.zeros([1, 3]) + info[0, 0] = h + info[0, 1] = w + # frame number + info[0, 2] = 1 + inputs[self._input_info_name] = info + + results = self._net.infer(inputs) + if len(results) == 1: + return results[self._output_blob_name].copy() + else: + return results.copy() diff --git a/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/python3 b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/python3 new file mode 100755 index 000000000000..b80f584712d7 --- /dev/null +++ b/serverless/open_model_zoo/intel/person-reidentification-retail/nuclio/python3 @@ -0,0 +1,6 @@ +#!/bin/bash + +args=$@ + +. /opt/intel/openvino/bin/setupvars.sh +/usr/bin/python3 $args From 66711b8a663db05972257ab4d44f8bfd4e6b9d9e Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sun, 21 Jun 2020 14:37:19 +0300 Subject: [PATCH 40/98] Model list in UI. --- .github/CODEOWNERS | 2 +- cvat-ui/src/actions/models-actions.ts | 92 +++---------------- cvat-ui/src/actions/plugins-actions.ts | 9 +- cvat-ui/src/utils/plugin-checker.ts | 11 +-- cvat/apps/lambda_manager/views.py | 34 ++++--- cvat/settings/base.py | 3 - .../nuclio/function.yaml | 1 + .../nuclio/function.yaml | 1 + .../public/yolov-v3-tf/nuclio/function.yaml | 1 + 9 files changed, 42 insertions(+), 112 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4b7ae8b1ded5..32af66a215ee 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -23,7 +23,7 @@ /datumaro/ @zhiltsov-max /cvat/apps/dataset_manager/ @zhiltsov-max -# Advanced components (e.g. OpenVINO) +# Advanced components (e.g. analytics) /components/ @azhavoro # Infrastructure diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index eebacbaedd1c..d193f1ce023b 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -117,96 +117,32 @@ const baseURL = core.config.backendAPI.slice(0, -7); export function getModelsAsync(): ThunkAction { return async (dispatch, getState): Promise => { - const state: CombinedState = getState(); - const OpenVINO = state.plugins.list.AUTO_ANNOTATION; - const RCNN = state.plugins.list.TF_ANNOTATION; - const MaskRCNN = state.plugins.list.TF_SEGMENTATION; - dispatch(modelsActions.getModels()); const models: Model[] = []; try { - if (OpenVINO) { - const response = await core.server.request( - `${baseURL}/auto_annotation/meta/get`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - data: JSON.stringify([]), + const response = await core.server.request( + `${baseURL}/api/v1/lambda/functions`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', }, - ); - + }, + ); - for (const model of response.models) { + for (const model of response) { + if (model.kind === 'detector') { models.push({ - id: model.id, - ownerID: model.owner, - primary: model.primary, + id: null, + ownerID: null, + primary: true, name: model.name, - uploadDate: model.uploadDate, - updateDate: model.updateDate, + uploadDate: '', + updateDate: '', labels: [...model.labels], }); } } - - if (RCNN) { - models.push({ - id: null, - ownerID: null, - primary: true, - name: PreinstalledModels.RCNN, - uploadDate: '', - updateDate: '', - labels: ['surfboard', 'car', 'skateboard', 'boat', 'clock', - 'cat', 'cow', 'knife', 'apple', 'cup', 'tv', - 'baseball_bat', 'book', 'suitcase', 'tennis_racket', - 'stop_sign', 'couch', 'cell_phone', 'keyboard', - 'cake', 'tie', 'frisbee', 'truck', 'fire_hydrant', - 'snowboard', 'bed', 'vase', 'teddy_bear', - 'toaster', 'wine_glass', 'traffic_light', - 'broccoli', 'backpack', 'carrot', 'potted_plant', - 'donut', 'umbrella', 'parking_meter', 'bottle', - 'sandwich', 'motorcycle', 'bear', 'banana', - 'person', 'scissors', 'elephant', 'dining_table', - 'toothbrush', 'toilet', 'skis', 'bowl', 'sheep', - 'refrigerator', 'oven', 'microwave', 'train', - 'orange', 'mouse', 'laptop', 'bench', 'bicycle', - 'fork', 'kite', 'zebra', 'baseball_glove', 'bus', - 'spoon', 'horse', 'handbag', 'pizza', 'sports_ball', - 'airplane', 'hair_drier', 'hot_dog', 'remote', - 'sink', 'dog', 'bird', 'giraffe', 'chair', - ], - }); - } - - if (MaskRCNN) { - models.push({ - id: null, - ownerID: null, - primary: true, - name: PreinstalledModels.MaskRCNN, - uploadDate: '', - updateDate: '', - labels: ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', - 'bus', 'train', 'truck', 'boat', 'traffic light', - 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', - 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', - 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', - 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', - 'kite', 'baseball bat', 'baseball glove', 'skateboard', - 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', - 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', - 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', - 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', - 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', - 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', - 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', - 'teddy bear', 'hair drier', 'toothbrush', - ], - }); - } } catch (error) { dispatch(modelsActions.getModelsFailed(error)); return; diff --git a/cvat-ui/src/actions/plugins-actions.ts b/cvat-ui/src/actions/plugins-actions.ts index 510f6590e827..4366bc58b24c 100644 --- a/cvat-ui/src/actions/plugins-actions.ts +++ b/cvat-ui/src/actions/plugins-actions.ts @@ -29,7 +29,7 @@ export function checkPluginsAsync(): ThunkAction { dispatch(pluginActions.checkPlugins()); const plugins: PluginObjects = { ANALYTICS: false, - AUTO_ANNOTATION: false, + AUTO_ANNOTATION: true, GIT_INTEGRATION: false, TF_ANNOTATION: false, TF_SEGMENTATION: false, @@ -39,17 +39,14 @@ export function checkPluginsAsync(): ThunkAction { const promises: Promise[] = [ PluginChecker.check(SupportedPlugins.ANALYTICS), - PluginChecker.check(SupportedPlugins.AUTO_ANNOTATION), PluginChecker.check(SupportedPlugins.GIT_INTEGRATION), - PluginChecker.check(SupportedPlugins.TF_ANNOTATION), - PluginChecker.check(SupportedPlugins.TF_SEGMENTATION), PluginChecker.check(SupportedPlugins.DEXTR_SEGMENTATION), PluginChecker.check(SupportedPlugins.REID), ]; const values = await Promise.all(promises); - [plugins.ANALYTICS, plugins.AUTO_ANNOTATION, plugins.GIT_INTEGRATION, plugins.TF_ANNOTATION, - plugins.TF_SEGMENTATION, plugins.DEXTR_SEGMENTATION, plugins.REID] = values; + [plugins.ANALYTICS, plugins.GIT_INTEGRATION, + plugins.DEXTR_SEGMENTATION, plugins.REID] = values; dispatch(pluginActions.checkedAllPlugins(plugins)); }; } diff --git a/cvat-ui/src/utils/plugin-checker.ts b/cvat-ui/src/utils/plugin-checker.ts index 2d244c250377..0b4fefa0f74d 100644 --- a/cvat-ui/src/utils/plugin-checker.ts +++ b/cvat-ui/src/utils/plugin-checker.ts @@ -26,15 +26,6 @@ class PluginChecker { case SupportedPlugins.GIT_INTEGRATION: { return isReachable(`${serverHost}/git/repository/meta/get`, 'OPTIONS'); } - case SupportedPlugins.AUTO_ANNOTATION: { - return isReachable(`${serverHost}/auto_annotation/meta/get`, 'OPTIONS'); - } - case SupportedPlugins.TF_ANNOTATION: { - return isReachable(`${serverHost}/tensorflow/annotation/meta/get`, 'OPTIONS'); - } - case SupportedPlugins.TF_SEGMENTATION: { - return isReachable(`${serverHost}/tensorflow/segmentation/meta/get`, 'OPTIONS'); - } case SupportedPlugins.DEXTR_SEGMENTATION: { return isReachable(`${serverHost}/api/v1/lambda/functions/public.dextr`, 'GET'); } @@ -42,7 +33,7 @@ class PluginChecker { return isReachable(`${serverHost}/analytics/app/kibana`, 'GET'); } case SupportedPlugins.REID: { - return isReachable(`${serverHost}/reid/enabled`, 'GET'); + return isReachable(`${serverHost}/api/v1/lambda/functions/omz.intel.reid`, 'GET'); } default: return false; diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 20f1915640b3..974cb3826516 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -44,8 +44,8 @@ def list(self): response = [LambdaFunction(self, item) for item in data.values()] return response - def get(self, name): - data = self._http(url=self.NUCLIO_ROOT_URL + '/' + name) + def get(self, id): + data = self._http(url=self.NUCLIO_ROOT_URL + '/' + id) response = LambdaFunction(self, data) return response @@ -54,8 +54,8 @@ def invoke(self, func, payload): class LambdaFunction: def __init__(self, gateway, data): - # name of the function (e.g. omz.public.yolo-v3) - self.name = data['metadata']['name'] + # ID of the function (e.g. omz.public.yolo-v3) + self.id = data['metadata']['name'] # type of the function (e.g. detector, interactor) self.kind = data['metadata']['annotations'].get('type') # dictionary of labels for the function (e.g. car, person) @@ -63,7 +63,7 @@ def __init__(self, gateway, data): labels = [item['name'] for item in spec] if len(labels) != len(set(labels)): raise ValidationError( - "`{}` lambda function has non-unique labels".format(self.name), + "`{}` lambda function has non-unique labels".format(self.id), code=status.HTTP_404_NOT_FOUND) self.labels = labels # state of the function @@ -72,15 +72,21 @@ def __init__(self, gateway, data): self.description = data['spec']['description'] # http port to access the serverless function self.port = data["status"]["httpPort"] + # framework which is used for the function (e.g. tensorflow, openvino) + self.framework = data['metadata']['annotations'].get('framework') + # display name for the function + self.name = data['metadata']['annotations'].get('name') self.gateway = gateway def to_dict(self): response = { - 'name': self.name, + 'id': self.id, 'kind': self.kind, 'labels': self.labels, 'state': self.state, - 'description': self.description + 'description': self.description, + 'framework': self.framework, + 'name': self.name, } return response @@ -148,7 +154,7 @@ def to_dict(self): return { "id": self.job.id, "function": { - "name": lambda_func.name if lambda_func else None, + "id": lambda_func.id if lambda_func else None, "threshold": self.job.kwargs.get("threshold"), "task": self.job.kwargs.get("task") }, @@ -238,7 +244,7 @@ def func_wrapper(*args, **kwargs): class FunctionViewSet(viewsets.ViewSet): lookup_value_regex = '[a-zA-Z0-9_.-]+' - lookup_field = 'name' + lookup_field = 'id' @return_response() def list(self, request): @@ -246,12 +252,12 @@ def list(self, request): return [f.to_dict() for f in gateway.list()] @return_response() - def retrieve(self, request, name): + def retrieve(self, request, id): gateway = LambdaGateway() - return gateway.get(name).to_dict() + return gateway.get(id).to_dict() @return_response() - def call(self, request, name): + def call(self, request, id): try: task = request.data['task'] points = request.data.get('points') @@ -259,12 +265,12 @@ def call(self, request, name): db_task = TaskModel.objects.get(pk=task) except (KeyError, ObjectDoesNotExist) as err: raise ValidationError( - '`{}` lambda function was run '.format(name) + + '`{}` lambda function was run '.format(id) + 'with wrong arguments ({})'.format(str(err)), code=status.HTTP_400_BAD_REQUEST) gateway = LambdaGateway() - lambda_func = gateway.get(name) + lambda_func = gateway.get(id) return lambda_func.invoke(db_task, frame, points) diff --git a/cvat/settings/base.py b/cvat/settings/base.py index 0d7b8c2148e4..1940e2caaf11 100644 --- a/cvat/settings/base.py +++ b/cvat/settings/base.py @@ -161,9 +161,6 @@ def generate_ssh_keys(): 'REGISTER_SERIALIZER': 'cvat.apps.restrictions.serializers.RestrictedRegisterSerializer' } -if 'yes' == os.environ.get('OPENVINO_TOOLKIT', 'no') and os.environ.get('REID_MODEL_DIR', ''): - INSTALLED_APPS += ['cvat.apps.reid'] - if os.getenv('DJANGO_LOG_VIEWER_HOST'): INSTALLED_APPS += ['cvat.apps.log_viewer'] diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml index 8d1839da857e..1bbf0e8a6025 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -2,6 +2,7 @@ metadata: name: omz.public.faster_rcnn_inception_v2_coco namespace: cvat annotations: + name: Faster RCNN type: detector framework: openvino spec: | diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml index c5421a7f5c68..45812c68d479 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -5,6 +5,7 @@ metadata: name: omz.public.mask_rcnn_inception_resnet_v2_atrous_coco namespace: cvat annotations: + name: Mask RCNN type: detector framework: openvino spec: | diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml index 1e1c9b869c0c..d8339a346926 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml @@ -2,6 +2,7 @@ metadata: name: omz.public.yolo-v3-tf namespace: cvat annotations: + name: YOLO v3 type: detector framework: openvino spec: | From 92463af28d4554af3f519118877b87c40fe22272 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Sun, 21 Jun 2020 14:44:24 +0300 Subject: [PATCH 41/98] Fixed the framework name (got it from lambda function). --- cvat-ui/src/actions/models-actions.ts | 1 + .../models-page/built-model-item.tsx | 2 +- .../src/components/models-page/top-bar.tsx | 20 ------------------- cvat-ui/src/reducers/interfaces.ts | 1 + 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index d193f1ce023b..55a4d55f22a5 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -137,6 +137,7 @@ export function getModelsAsync(): ThunkAction { ownerID: null, primary: true, name: model.name, + framework: model.framework, uploadDate: '', updateDate: '', labels: [...model.labels], diff --git a/cvat-ui/src/components/models-page/built-model-item.tsx b/cvat-ui/src/components/models-page/built-model-item.tsx index 83dda54e171e..f100dfe5a4e0 100644 --- a/cvat-ui/src/components/models-page/built-model-item.tsx +++ b/cvat-ui/src/components/models-page/built-model-item.tsx @@ -20,7 +20,7 @@ export default function BuiltModelItemComponent(props: Props): JSX.Element { return ( - Tensorflow + {model.framework} diff --git a/cvat-ui/src/components/models-page/top-bar.tsx b/cvat-ui/src/components/models-page/top-bar.tsx index b58abd739522..85158a762fba 100644 --- a/cvat-ui/src/components/models-page/top-bar.tsx +++ b/cvat-ui/src/components/models-page/top-bar.tsx @@ -24,26 +24,6 @@ function TopBarComponent(props: Props): JSX.Element { Models - - { installedAutoAnnotation - && ( - - )} - ); } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 6031435975ce..5830730399d3 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -140,6 +140,7 @@ export interface Model { uploadDate: string; updateDate: string; labels: string[]; + framework: string; } export enum RQStatus { From 54f64e9fd263225d7fc7a61e50091fdfd8592754 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 22 Jun 2020 18:52:24 +0300 Subject: [PATCH 42/98] Add maxRequestBodySize for functions, remove redundant code from UI for auto_annotation. --- cvat-ui/src/actions/models-actions.ts | 125 +++++------------- cvat-ui/src/reducers/interfaces.ts | 1 + cvat/apps/lambda_manager/views.py | 42 +++--- .../nuclio/function.yaml | 2 + .../nuclio/function.yaml | 2 + .../public/yolov-v3-tf/nuclio/function.yaml | 2 + serverless/public/dextr/nuclio/function.yaml | 2 + 7 files changed, 67 insertions(+), 109 deletions(-) diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index 55a4d55f22a5..55ee248cddf8 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -96,7 +96,7 @@ export const modelsActions = { taskID, }, ), - cancelInferenceFaild: (taskID: number, error: any) => createAction( + cancelInferenceFailed: (taskID: number, error: any) => createAction( ModelsActionTypes.CANCEL_INFERENCE_FAILED, { taskID, error, @@ -133,7 +133,7 @@ export function getModelsAsync(): ThunkAction { for (const model of response) { if (model.kind === 'detector') { models.push({ - id: null, + id: model.id, ownerID: null, primary: true, name: model.name, @@ -342,69 +342,22 @@ export function getInferenceStatusAsync(tasks: number[]): ThunkAction { })); } - const state: CombinedState = getState(); - const OpenVINO = state.plugins.list.AUTO_ANNOTATION; - const RCNN = state.plugins.list.TF_ANNOTATION; - const MaskRCNN = state.plugins.list.TF_SEGMENTATION; - const dispatchCallback = (action: ModelsActions): void => { dispatch(action); }; try { - if (OpenVINO) { - const response = await core.server.request( - `${baseURL}/auto_annotation/meta/get`, { - method: 'POST', - data: JSON.stringify(tasks), - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - - parse(response.run, ModelType.OPENVINO) - .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) - .forEach((inferenceMeta: InferenceMeta): void => { - subscribe(inferenceMeta, dispatchCallback); - }); - } - - if (RCNN) { - const response = await core.server.request( - `${baseURL}/tensorflow/annotation/meta/get`, { - method: 'POST', - data: JSON.stringify(tasks), - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - - parse(response, ModelType.RCNN) - .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) - .forEach((inferenceMeta: InferenceMeta): void => { - subscribe(inferenceMeta, dispatchCallback); - }); - } - - if (MaskRCNN) { - const response = await core.server.request( - `${baseURL}/tensorflow/segmentation/meta/get`, { - method: 'POST', - data: JSON.stringify(tasks), - headers: { - 'Content-Type': 'application/json', - }, - }, - ); + const response = await core.server.request( + `${baseURL}/api/v1/lambda/requests`, { + method: 'GET', + }, + ); - parse(response, ModelType.MASK_RCNN) - .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) - .forEach((inferenceMeta: InferenceMeta): void => { - subscribe(inferenceMeta, dispatchCallback); - }); - } + parse(response.run, ModelType.OPENVINO) + .filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active) + .forEach((inferenceMeta: InferenceMeta): void => { + subscribe(inferenceMeta, dispatchCallback); + }); } catch (error) { dispatch(modelsActions.fetchMetaFailed(error)); } @@ -421,28 +374,20 @@ export function startInferenceAsync( ): ThunkAction { return async (dispatch): Promise => { try { - if (model.name === PreinstalledModels.RCNN) { - await core.server.request( - `${baseURL}/tensorflow/annotation/create/task/${taskInstance.id}`, - ); - } else if (model.name === PreinstalledModels.MaskRCNN) { - await core.server.request( - `${baseURL}/tensorflow/segmentation/create/task/${taskInstance.id}`, - ); - } else { - await core.server.request( - `${baseURL}/auto_annotation/start/${model.id}/${taskInstance.id}`, { - method: 'POST', - data: JSON.stringify({ - reset: cleanOut, - labels: mapping, - }), - headers: { - 'Content-Type': 'application/json', - }, + await core.server.request( + `${baseURL}/api/v1/lambda/requests`, { + method: 'POST', + data: JSON.stringify({ + cleanup: cleanOut, + mapping, + task: taskInstance.id, + function: model.id, + }), + headers: { + 'Content-Type': 'application/json', }, - ); - } + }, + ); dispatch(getInferenceStatusAsync([taskInstance.id])); } catch (error) { @@ -456,19 +401,11 @@ export function cancelInferenceAsync(taskID: number): ThunkAction { try { const inference = getState().models.inferences[taskID]; if (inference) { - if (inference.modelType === ModelType.OPENVINO) { - await core.server.request( - `${baseURL}/auto_annotation/cancel/${taskID}`, - ); - } else if (inference.modelType === ModelType.RCNN) { - await core.server.request( - `${baseURL}/tensorflow/annotation/cancel/task/${taskID}`, - ); - } else if (inference.modelType === ModelType.MASK_RCNN) { - await core.server.request( - `${baseURL}/tensorflow/segmentation/cancel/task/${taskID}`, - ); - } + await core.server.request( + `${baseURL}/api/v1/lambda/requests/${inference.id}`, { + method: 'DELETE', + }, + ); if (timers[taskID]) { clearTimeout(timers[taskID]); @@ -478,7 +415,7 @@ export function cancelInferenceAsync(taskID: number): ThunkAction { dispatch(modelsActions.cancelInferenceSuccess(taskID)); } catch (error) { - dispatch(modelsActions.cancelInferenceFaild(taskID, error)); + dispatch(modelsActions.cancelInferenceFailed(taskID, error)); } }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 5830730399d3..e8c1b20b8662 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -162,6 +162,7 @@ export interface ActiveInference { progress: number; error: string; modelType: ModelType; + id: string; } export interface ModelsState { diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index 974cb3826516..ae8eaa04b416 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -71,11 +71,11 @@ def __init__(self, gateway, data): # description of the function self.description = data['spec']['description'] # http port to access the serverless function - self.port = data["status"]["httpPort"] + self.port = data["status"].get("httpPort") # framework which is used for the function (e.g. tensorflow, openvino) self.framework = data['metadata']['annotations'].get('framework') # display name for the function - self.name = data['metadata']['annotations'].get('name') + self.name = data['metadata']['annotations'].get('name', self.id) self.gateway = gateway def to_dict(self): @@ -88,21 +88,30 @@ def to_dict(self): 'framework': self.framework, 'name': self.name, } + return response - def invoke(self, db_task, frame, points=None): + def invoke(self, db_task, frame, quality, points=None): payload = { - 'image': self._get_image(db_task, frame), + 'image': self._get_image(db_task, frame, quality), 'points': points } return self.gateway.invoke(self, payload) - def _get_image(self, db_task, frame): + def _get_image(self, db_task, frame, quality): + if quality is None or quality == "original": + quality = FrameProvider.Quality.ORIGINAL + elif quality == "original": + quality = FrameProvider.Quality.COMPRESSED + else: + raise ValidationError( + '`{}` lambda function was run '.format(self.id) + + 'with wrong arguments (quality={})'.format(quality), + code=status.HTTP_400_BAD_REQUEST) + frame_provider = FrameProvider(db_task.data) - # FIXME: now we cannot use the original quality because nuclio has body - # limit size by default 4Mb (from FastHTTP). - image = frame_provider.get_frame(frame, quality=FrameProvider.Quality.COMPRESSED) + image = frame_provider.get_frame(frame, quality=quality) return base64.b64encode(image[0].getvalue()).decode('utf-8') @@ -117,10 +126,10 @@ def get_jobs(self): # TODO: protect from running multiple times for the same task # Only one job for an annotation task - def enqueue(self, lambda_func, threshold, task): + def enqueue(self, lambda_func, threshold, task, quality): queue = self._get_queue() # LambdaJob(None) is a workaround for python-rq. It has multiple issues - # with invocation of non trivial functions. For example, it cannot run + # with invocation of non-trivial functions. For example, it cannot run # staticmethod, it cannot run a callable class. Thus I provide an object # which has __call__ function. job = queue.create_job(LambdaJob(None), @@ -128,7 +137,8 @@ def enqueue(self, lambda_func, threshold, task): kwargs = { "function": lambda_func, "threshold": threshold, - "task": task + "task": task, + "quality": quality }) queue.enqueue_job(job) @@ -169,13 +179,13 @@ def delete(self): self.job.delete() @staticmethod - def __call__(function, threshold, task): + def __call__(function, threshold, task, quality): # TODO: need to remove annotations if clear flag is True # TODO: need logging db_task = TaskModel.objects.get(pk=task) # TODO: check tasks with a frame step for frame in range(db_task.data.size): - annotations = function.invoke(db_task, frame) + annotations = function.invoke(db_task, frame, quality) # TODO: optimize # TODO: need user mapping between model labels and task labels db_labels = db_task.label_set.prefetch_related("attributespec_set").all() @@ -262,6 +272,7 @@ def call(self, request, id): task = request.data['task'] points = request.data.get('points') frame = request.data['frame'] + quality = request.data.get('quality') db_task = TaskModel.objects.get(pk=task) except (KeyError, ObjectDoesNotExist) as err: raise ValidationError( @@ -272,7 +283,7 @@ def call(self, request, id): gateway = LambdaGateway() lambda_func = gateway.get(id) - return lambda_func.invoke(db_task, frame, points) + return lambda_func.invoke(db_task, frame, quality, points) class RequestViewSet(viewsets.ViewSet): @return_response() @@ -285,11 +296,12 @@ def create(self, request): function = request.data['function'] threshold = request.data.get('threshold') task = request.data['task'] + quality = request.data.get("quality") gateway = LambdaGateway() queue = LambdaQueue() lambda_func = gateway.get(function) - job = queue.enqueue(lambda_func, threshold, task) + job = queue.enqueue(lambda_func, threshold, task, quality) return job.to_dict() diff --git a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml index 1bbf0e8a6025..50e92d0ec41d 100644 --- a/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/faster_rcnn_inception_v2_coco/nuclio/function.yaml @@ -124,6 +124,8 @@ spec: maxWorkers: 2 kind: "http" workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB platform: attributes: diff --git a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml index 45812c68d479..7f186ba9f1a1 100644 --- a/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio/function.yaml @@ -131,6 +131,8 @@ spec: maxWorkers: 2 kind: "http" workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB platform: attributes: diff --git a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml index d8339a346926..1236f410e03a 100644 --- a/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml +++ b/serverless/open_model_zoo/public/yolov-v3-tf/nuclio/function.yaml @@ -124,6 +124,8 @@ spec: maxWorkers: 2 kind: "http" workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB platform: attributes: diff --git a/serverless/public/dextr/nuclio/function.yaml b/serverless/public/dextr/nuclio/function.yaml index 79caf7d8e332..051b5b3bdef4 100644 --- a/serverless/public/dextr/nuclio/function.yaml +++ b/serverless/public/dextr/nuclio/function.yaml @@ -43,6 +43,8 @@ spec: maxWorkers: 2 kind: "http" workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB platform: attributes: From e13682792f1c7d77606c937993bf8e4f9eb1b129 Mon Sep 17 00:00:00 2001 From: Nikita Manovich Date: Mon, 22 Jun 2020 20:55:59 +0300 Subject: [PATCH 43/98] Update view of models page. --- cvat-ui/src/actions/models-actions.ts | 5 +- .../models-page/uploaded-model-item.tsx | 48 ++++--------------- .../models-page/uploaded-models-list.tsx | 25 +++------- cvat-ui/src/reducers/interfaces.ts | 7 ++- 4 files changed, 20 insertions(+), 65 deletions(-) diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index 55ee248cddf8..8f4460fabcc3 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -134,13 +134,12 @@ export function getModelsAsync(): ThunkAction { if (model.kind === 'detector') { models.push({ id: model.id, - ownerID: null, primary: true, name: model.name, + description: model.description, framework: model.framework, - uploadDate: '', - updateDate: '', labels: [...model.labels], + type: model.kind, }); } } diff --git a/cvat-ui/src/components/models-page/uploaded-model-item.tsx b/cvat-ui/src/components/models-page/uploaded-model-item.tsx index b108ec8ddf18..b47071b6804e 100644 --- a/cvat-ui/src/components/models-page/uploaded-model-item.tsx +++ b/cvat-ui/src/components/models-page/uploaded-model-item.tsx @@ -6,47 +6,35 @@ import React from 'react'; import { Row, Col } from 'antd/lib/grid'; import Tag from 'antd/lib/tag'; import Select from 'antd/lib/select'; -import Icon from 'antd/lib/icon'; -import Menu from 'antd/lib/menu'; -import Dropdown from 'antd/lib/dropdown'; import Text from 'antd/lib/typography/Text'; -import moment from 'moment'; - -import { MenuIcon } from 'icons'; import { Model } from 'reducers/interfaces'; interface Props { model: Model; - owner: any; - onDelete(): void; } export default function UploadedModelItem(props: Props): JSX.Element { const { model, - owner, - onDelete, } = props; return ( - - OpenVINO + + {model.framework} - + {model.name} - - {owner ? owner.username : 'undefined'} - + + {model.type} + - - - {moment(model.uploadDate).format('MMMM Do YYYY')} - + + {model.description} )} - - - - - - { getFieldDecorator('global', { - initialValue: false, - valuePropName: 'checked', - })( - - - Load globally - - , - )} - - - - - - ); - } -} - -export default Form.create()(CreateModelForm); diff --git a/cvat-ui/src/components/create-model-page/create-model-page.tsx b/cvat-ui/src/components/create-model-page/create-model-page.tsx deleted file mode 100644 index 7c81aca0f878..000000000000 --- a/cvat-ui/src/components/create-model-page/create-model-page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import './styles.scss'; -import React from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Text from 'antd/lib/typography/Text'; - -import { ModelFiles } from 'reducers/interfaces'; -import CreateModelContent from './create-model-content'; - -interface Props { - createModel(name: string, files: ModelFiles, global: boolean): void; - isAdmin: boolean; - modelCreatingStatus: string; -} - -export default function CreateModelPageComponent(props: Props): JSX.Element { - const { - isAdmin, - modelCreatingStatus, - createModel, - } = props; - - return ( - - - Upload a new model - - - - ); -} diff --git a/cvat-ui/src/components/create-model-page/styles.scss b/cvat-ui/src/components/create-model-page/styles.scss deleted file mode 100644 index 65df22b23d33..000000000000 --- a/cvat-ui/src/components/create-model-page/styles.scss +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -@import '../../base.scss'; - -.cvat-create-model-form-wrapper { - text-align: center; - margin-top: 40px; - overflow-y: auto; - height: 90%; - - > div > span { - font-size: 36px; - } - - .cvat-create-model-content { - margin-top: 20px; - width: 100%; - height: auto; - border: 1px solid $border-color-1; - border-radius: 3px; - padding: 20px; - background: $background-color-1; - text-align: initial; - - > div:nth-child(1) > i { - float: right; - font-size: 20px; - color: $danger-icon-color; - } - - > div:nth-child(4) { - margin-top: 10px; - } - - > div:nth-child(6) > button { - margin-top: 10px; - float: right; - width: 120px; - } - } -} diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 9eeecd785098..f8d194d58184 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -18,7 +18,6 @@ import TasksPageContainer from 'containers/tasks-page/tasks-page'; import CreateTaskPageContainer from 'containers/create-task-page/create-task-page'; import TaskPageContainer from 'containers/task-page/task-page'; import ModelsPageContainer from 'containers/models-page/models-page'; -import CreateModelPageContainer from 'containers/create-model-page/create-model-page'; import AnnotationPageContainer from 'containers/annotation-page/annotation-page'; import LoginPageContainer from 'containers/login-page/login-page'; import RegisterPageContainer from 'containers/register-page/register-page'; @@ -50,9 +49,6 @@ interface CVATAppProps { usersFetching: boolean; aboutInitialized: boolean; aboutFetching: boolean; - installedAutoAnnotation: boolean; - installedTFAnnotation: boolean; - installedTFSegmentation: boolean; userAgreementsFetching: boolean; userAgreementsInitialized: boolean; notifications: NotificationsState; @@ -221,9 +217,6 @@ class CVATApplication extends React.PureComponent - {withModels - && } - {installedAutoAnnotation - && } + diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index dc01678b80b1..9d3e5fee4302 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -24,9 +24,6 @@ interface HeaderContainerProps { switchSettingsDialog: (show: boolean) => void; logoutFetching: boolean; installedAnalytics: boolean; - installedAutoAnnotation: boolean; - installedTFAnnotation: boolean; - installedTFSegmentation: boolean; serverHost: string; username: string; toolName: string; @@ -43,9 +40,6 @@ type Props = HeaderContainerProps & RouteComponentProps; function HeaderContainer(props: Props): JSX.Element { const { - installedTFSegmentation, - installedAutoAnnotation, - installedTFAnnotation, installedAnalytics, username, toolName, @@ -62,10 +56,6 @@ function HeaderContainer(props: Props): JSX.Element { switchSettingsDialog, } = props; - const renderModels = installedAutoAnnotation - || installedTFAnnotation - || installedTFSegmentation; - const { CHANGELOG_URL, LICENSE_URL, @@ -172,19 +162,16 @@ function HeaderContainer(props: Props): JSX.Element { > Tasks - { renderModels - && ( - - )} + { installedAnalytics && (