From 52f87c7fa2d54b25a6b075cf549ce960ed63b59d Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Thu, 1 Aug 2019 14:05:04 +0800 Subject: [PATCH 01/11] refactor(base): make pipelineencoder more general and allow pipelinepreprocessor --- gnes/base/__init__.py | 68 +++++++++++++++++++++++++- gnes/encoder/__init__.py | 1 - gnes/encoder/base.py | 69 ++------------------------- gnes/preprocessor/__init__.py | 1 + gnes/preprocessor/base.py | 18 ++++++- gnes/preprocessor/video/ffmpeg.py | 6 +-- gnes/preprocessor/video/shotdetect.py | 8 ++-- tests/test_pipelinepreprocess.py | 45 +++++++++++++++++ 8 files changed, 140 insertions(+), 76 deletions(-) create mode 100644 tests/test_pipelinepreprocess.py diff --git a/gnes/base/__init__.py b/gnes/base/__init__.py index 872201b0..fcef4e89 100644 --- a/gnes/base/__init__.py +++ b/gnes/base/__init__.py @@ -22,7 +22,7 @@ import tempfile import uuid from functools import wraps -from typing import Dict, Any, Union, TextIO, TypeVar, Type +from typing import Dict, Any, Union, TextIO, TypeVar, Type, List, Callable import ruamel.yaml.constructor @@ -355,3 +355,69 @@ def _dump_instance_to_yaml(data): if p: r['gnes_config'] = p return r + + def _copy_from(self, x: 'TrainableBase') -> None: + pass + + +class CompositionalTrainableBase(TrainableBase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._component = None # type: List[T] + + @property + def component(self) -> Union[List[T], Dict[str, T]]: + return self._component + + @property + def is_pipeline(self): + return isinstance(self.component, list) + + @component.setter + def component(self, comps: Callable[[], Union[list, dict]]): + if not callable(comps): + raise TypeError('component must be a callable function that returns ' + 'a List[BaseEncoder]') + if not getattr(self, 'init_from_yaml', False): + self._component = comps() + else: + self.logger.info('component is omitted from construction, ' + 'as it is initialized from yaml config') + + def close(self): + super().close() + # pipeline + if isinstance(self.component, list): + for be in self.component: + be.close() + # no typology + elif isinstance(self.component, dict): + for be in self.component.values(): + be.close() + elif self.component is None: + pass + else: + raise TypeError('component must be dict or list, received %s' % type(self.component)) + + def _copy_from(self, x: T): + if isinstance(self.component, list): + for be1, be2 in zip(self.component, x.component): + be1._copy_from(be2) + elif isinstance(self.component, dict): + for k, v in self.component.items(): + v._copy_from(x.component[k]) + else: + raise TypeError('component must be dict or list, received %s' % type(self.component)) + + @classmethod + def to_yaml(cls, representer, data): + tmp = super()._dump_instance_to_yaml(data) + tmp['component'] = data.component + return representer.represent_mapping('!' + cls.__name__, tmp) + + @classmethod + def from_yaml(cls, constructor, node): + obj, data, from_dump = super()._get_instance_from_yaml(constructor, node) + if not from_dump and 'component' in data: + obj.component = lambda: data['component'] + return obj diff --git a/gnes/encoder/__init__.py b/gnes/encoder/__init__.py index e77f2411..691623f1 100644 --- a/gnes/encoder/__init__.py +++ b/gnes/encoder/__init__.py @@ -35,7 +35,6 @@ 'BaseBinaryEncoder': 'base', 'BaseTextEncoder': 'base', 'BaseNumericEncoder': 'base', - 'CompositionalEncoder': 'base', 'PipelineEncoder': 'base', 'HashEncoder': 'numeric.hash', 'BasePytorchEncoder': 'image.base', diff --git a/gnes/encoder/base.py b/gnes/encoder/base.py index 964e237e..469a3c1b 100644 --- a/gnes/encoder/base.py +++ b/gnes/encoder/base.py @@ -16,11 +16,11 @@ # pylint: disable=low-comment-ratio -from typing import List, Any, Union, Dict, Callable +from typing import List, Any import numpy as np -from ..base import TrainableBase +from ..base import TrainableBase, CompositionalTrainableBase class BaseEncoder(TrainableBase): @@ -58,70 +58,7 @@ def encode(self, data: np.ndarray, *args, **kwargs) -> bytes: return data.tobytes() -class CompositionalEncoder(BaseEncoder): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._component = None # type: List['BaseEncoder'] - - @property - def component(self) -> Union[List['BaseEncoder'], Dict[str, 'BaseEncoder']]: - return self._component - - @property - def is_pipeline(self): - return isinstance(self.component, list) - - @component.setter - def component(self, comps: Callable[[], Union[list, dict]]): - if not callable(comps): - raise TypeError('component must be a callable function that returns ' - 'a List[BaseEncoder]') - if not getattr(self, 'init_from_yaml', False): - self._component = comps() - else: - self.logger.info('component is omitted from construction, ' - 'as it is initialized from yaml config') - - def close(self): - super().close() - # pipeline - if isinstance(self.component, list): - for be in self.component: - be.close() - # no typology - elif isinstance(self.component, dict): - for be in self.component.values(): - be.close() - elif self.component is None: - pass - else: - raise TypeError('component must be dict or list, received %s' % type(self.component)) - - def _copy_from(self, x: 'CompositionalEncoder'): - if isinstance(self.component, list): - for be1, be2 in zip(self.component, x.component): - be1._copy_from(be2) - elif isinstance(self.component, dict): - for k, v in self.component.items(): - v._copy_from(x.component[k]) - else: - raise TypeError('component must be dict or list, received %s' % type(self.component)) - - @classmethod - def to_yaml(cls, representer, data): - tmp = super()._dump_instance_to_yaml(data) - tmp['component'] = data.component - return representer.represent_mapping('!' + cls.__name__, tmp) - - @classmethod - def from_yaml(cls, constructor, node): - obj, data, from_dump = super()._get_instance_from_yaml(constructor, node) - if not from_dump and 'component' in data: - obj.component = lambda: data['component'] - return obj - - -class PipelineEncoder(CompositionalEncoder): +class PipelineEncoder(CompositionalTrainableBase): def encode(self, data: Any, *args, **kwargs) -> Any: if not self.component: raise NotImplementedError diff --git a/gnes/preprocessor/__init__.py b/gnes/preprocessor/__init__.py index 6764e9b0..63222041 100644 --- a/gnes/preprocessor/__init__.py +++ b/gnes/preprocessor/__init__.py @@ -20,6 +20,7 @@ _cls2file_map = { 'BasePreprocessor': 'base', + 'PipelinePreprocessor': 'base', 'TextPreprocessor': 'text.simple', 'BaseImagePreprocessor': 'image.base', 'BaseTextPreprocessor': 'text.base', diff --git a/gnes/preprocessor/base.py b/gnes/preprocessor/base.py index f2d0e61d..356c728e 100644 --- a/gnes/preprocessor/base.py +++ b/gnes/preprocessor/base.py @@ -21,7 +21,7 @@ import numpy as np from PIL import Image -from ..base import TrainableBase +from ..base import TrainableBase, CompositionalTrainableBase from ..proto import gnes_pb2, array2blob @@ -38,6 +38,22 @@ def apply(self, doc: 'gnes_pb2.Document') -> None: doc.doc_type = self.doc_type +class PipelinePreprocessor(CompositionalTrainableBase): + def apply(self, doc: 'gnes_pb2.Document') -> None: + if not self.component: + raise NotImplementedError + for be in self.component: + be.apply(doc) + + def train(self, data, *args, **kwargs): + if not self.component: + raise NotImplementedError + for idx, be in enumerate(self.component): + be.train(data, *args, **kwargs) + if idx + 1 < len(self.component): + data = be.apply(data, *args, **kwargs) + + class BaseUnaryPreprocessor(BasePreprocessor): def __init__(self, doc_type: int, *args, **kwargs): diff --git a/gnes/preprocessor/video/ffmpeg.py b/gnes/preprocessor/video/ffmpeg.py index c21439b9..08e17c58 100644 --- a/gnes/preprocessor/video/ffmpeg.py +++ b/gnes/preprocessor/video/ffmpeg.py @@ -25,7 +25,7 @@ class FFmpegPreprocessor(BaseVideoPreprocessor): def __init__(self, - frame_size: str = "192*168", + frame_size: str = '192*168', duplicate_rm: bool = True, use_phash_weight: bool = False, phash_thresh: int = 5, @@ -48,8 +48,8 @@ def apply(self, doc: 'gnes_pb2.Document') -> None: frames = get_video_frames( doc.raw_bytes, s=self.frame_size, - vsync=self._ffmpeg_kwargs.get("vsync", "vfr"), - vf=self._ffmpeg_kwargs.get("vf", "select=eq(pict_type\\,I)")) + vsync=self._ffmpeg_kwargs.get('vsync', 'vfr'), + vf=self._ffmpeg_kwargs.get('vf', 'select=eq(pict_type\\,I)')) # remove dupliated key frames by phash value if self.duplicate_rm: diff --git a/gnes/preprocessor/video/shotdetect.py b/gnes/preprocessor/video/shotdetect.py index 377d8ee5..c1fdca80 100644 --- a/gnes/preprocessor/video/shotdetect.py +++ b/gnes/preprocessor/video/shotdetect.py @@ -26,9 +26,9 @@ class ShotDetectPreprocessor(BaseVideoPreprocessor): store_args_kwargs = True def __init__(self, - frame_size: str = "192*168", - descriptor: str = "block_hsv_histogram", - distance_metric: str = "bhattacharya", + frame_size: str = '192*168', + descriptor: str = 'block_hsv_histogram', + distance_metric: str = 'bhattacharya', *args, **kwargs): super().__init__(*args, **kwargs) @@ -47,7 +47,7 @@ def apply(self, doc: 'gnes_pb2.Document') -> None: frames = get_video_frames( doc.raw_bytes, s=self.frame_size, - vsync="vfr", + vsync='vfr', vf='select=eq(pict_type\\,I)') descriptors = [] diff --git a/tests/test_pipelinepreprocess.py b/tests/test_pipelinepreprocess.py new file mode 100644 index 00000000..b6a11ef3 --- /dev/null +++ b/tests/test_pipelinepreprocess.py @@ -0,0 +1,45 @@ +import os +import unittest + +from gnes.preprocessor.base import BasePreprocessor, PipelinePreprocessor +from gnes.proto import gnes_pb2 + + +class P1(BasePreprocessor): + def apply(self, doc: 'gnes_pb2.Document'): + doc.doc_id += 1 + + +class P2(BasePreprocessor): + def apply(self, doc: 'gnes_pb2.Document'): + doc.doc_id *= 3 + + +class TestPartition(unittest.TestCase): + def setUp(self): + self.dirname = os.path.dirname(__file__) + self.p3_name = 'pipe-p12' + self.yml_dump_path = os.path.join(self.dirname, '%s.yml' % self.p3_name) + self.bin_dump_path = os.path.join(self.dirname, '%s.bin' % self.p3_name) + + def tearDown(self): + if os.path.exists(self.yml_dump_path): + os.remove(self.yml_dump_path) + if os.path.exists(self.bin_dump_path): + os.remove(self.bin_dump_path) + + def test_pipelinepreproces(self): + p3 = PipelinePreprocessor() + p3.component = lambda: [P1(), P2()] + d = gnes_pb2.Document() + d.doc_id = 1 + p3.apply(d) + self.assertEqual(d.doc_id, 6) + + p3.name = self.p3_name + p3.dump_yaml() + p3.dump() + + p4 = BasePreprocessor.load_yaml(p3.yaml_full_path) + p4.apply(d) + self.assertEqual(d.doc_id, 21) From 7126d496e1195c7469e417672329145e326d5c1c Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Thu, 1 Aug 2019 15:05:06 +0800 Subject: [PATCH 02/11] refactor(preprocessor): separate resize logic from the unary preprocessor --- gnes/base/__init__.py | 2 +- gnes/preprocessor/__init__.py | 1 + gnes/preprocessor/base.py | 13 ++++-- gnes/preprocessor/image/base.py | 2 +- gnes/preprocessor/image/resize.py | 38 ++++++++++++++++++ gnes/preprocessor/image/segmentation.py | 6 +-- gnes/preprocessor/image/sliding_window.py | 2 - tests/imgs/test.zip | Bin 23159 -> 39433 bytes tests/test_image_preprocessor.py | 28 +++++++++++-- tests/yaml/resize-image-prep.yml | 9 +++++ ...ngleton.yml => img_preprocessor_unary.yml} | 0 11 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 gnes/preprocessor/image/resize.py create mode 100644 tests/yaml/resize-image-prep.yml rename yaml-example/component/{img_preprocessor_singleton.yml => img_preprocessor_unary.yml} (100%) diff --git a/gnes/base/__init__.py b/gnes/base/__init__.py index fcef4e89..9a6d70b9 100644 --- a/gnes/base/__init__.py +++ b/gnes/base/__init__.py @@ -28,7 +28,7 @@ from ..helper import set_logger, profiling, yaml, parse_arg, load_contrib_module -__all__ = ['TrainableBase'] +__all__ = ['TrainableBase', 'CompositionalTrainableBase'] T = TypeVar('T', bound='TrainableBase') diff --git a/gnes/preprocessor/__init__.py b/gnes/preprocessor/__init__.py index 63222041..66a16fdb 100644 --- a/gnes/preprocessor/__init__.py +++ b/gnes/preprocessor/__init__.py @@ -29,6 +29,7 @@ 'WeightedSlidingPreprocessor': 'image.sliding_window', 'SegmentPreprocessor': 'image.segmentation', 'BaseUnaryPreprocessor': 'base', + 'ResizeChunkPreprocessor': 'image.resize', 'BaseVideoPreprocessor': 'video.base', 'FFmpegPreprocessor': 'video.ffmpeg', 'ShotDetectPreprocessor': 'video.shotdetect', diff --git a/gnes/preprocessor/base.py b/gnes/preprocessor/base.py index 356c728e..01952112 100644 --- a/gnes/preprocessor/base.py +++ b/gnes/preprocessor/base.py @@ -28,14 +28,21 @@ class BasePreprocessor(TrainableBase): doc_type = gnes_pb2.Document.UNKNOWN - def __init__(self, start_doc_id: int = 0, random_doc_id: bool = True, *args, **kwargs): + def __init__(self, start_doc_id: int = 0, + random_doc_id: bool = True, + uniform_doc_weight: bool = True, + *args, **kwargs): super().__init__(*args, **kwargs) self.start_doc_id = start_doc_id self.random_doc_id = random_doc_id + self.uniform_doc_weight = uniform_doc_weight def apply(self, doc: 'gnes_pb2.Document') -> None: doc.doc_id = self.start_doc_id if not self.random_doc_id else random.randint(0, ctypes.c_uint(-1).value) doc.doc_type = self.doc_type + if not doc.weight and self.uniform_doc_weight: + doc.weight = 1.0 + self.start_doc_id += 1 class PipelinePreprocessor(CompositionalTrainableBase): @@ -55,11 +62,10 @@ def train(self, data, *args, **kwargs): class BaseUnaryPreprocessor(BasePreprocessor): + is_trained = True def __init__(self, doc_type: int, *args, **kwargs): super().__init__(*args, **kwargs) - self.target_img_size = 224 - self.is_trained = True self.doc_type = doc_type def apply(self, doc: 'gnes_pb2.Document'): @@ -78,7 +84,6 @@ def raw_to_chunk(self, chunk: 'gnes_pb2.Chunk', raw_bytes: bytes): chunk.text = raw_bytes.decode() elif self.doc_type == gnes_pb2.Document.IMAGE: img = np.array(Image.open(io.BytesIO(raw_bytes))) - img = np.array(Image.fromarray(img).resize((self.target_img_size, self.target_img_size))) chunk.blob.CopyFrom(array2blob(img)) elif self.doc_type == gnes_pb2.Document.VIDEO: raise NotImplementedError diff --git a/gnes/preprocessor/image/base.py b/gnes/preprocessor/image/base.py index a557cbe9..783b2eb7 100644 --- a/gnes/preprocessor/image/base.py +++ b/gnes/preprocessor/image/base.py @@ -46,4 +46,4 @@ def _get_all_subarea(image): index = [[x, y, x + 1, y + 1] for [x, y] in product(range(len(x_list) - 1), range(len(y_list) - 1))] all_subareas = [[x_list[idx[0]], y_list[idx[1]], x_list[idx[2]], y_list[idx[3]]] for idx in index] - return all_subareas, index \ No newline at end of file + return all_subareas, index diff --git a/gnes/preprocessor/image/resize.py b/gnes/preprocessor/image/resize.py new file mode 100644 index 00000000..34846c1a --- /dev/null +++ b/gnes/preprocessor/image/resize.py @@ -0,0 +1,38 @@ +# Tencent is pleased to support the open source community by making GNES available. +# +# Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from PIL import Image + +from .base import BaseImagePreprocessor +from ...proto import gnes_pb2, blob2array, array2blob + + +class ResizeChunkPreprocessor(BaseImagePreprocessor): + + def __init__(self, + target_width: int = 224, + target_height: int = 224, + *args, **kwargs): + super().__init__(*args, **kwargs) + self.target_width = target_width + self.target_height = target_height + + def apply(self, doc: 'gnes_pb2.Document') -> None: + super().apply(doc) + for c in doc.chunks: + img = blob2array(c.blob) + img = np.array(Image.fromarray(img).resize((self.target_width, self.target_height))) + c.blob.CopyFrom(array2blob(img)) diff --git a/gnes/preprocessor/image/segmentation.py b/gnes/preprocessor/image/segmentation.py index b5a6392f..d66978c8 100644 --- a/gnes/preprocessor/image/segmentation.py +++ b/gnes/preprocessor/image/segmentation.py @@ -28,13 +28,11 @@ class SegmentPreprocessor(BaseImagePreprocessor): def __init__(self, model_name: str, model_dir: str, - target_img_size: int = 224, _use_cuda: bool = False, *args, **kwargs): super().__init__(*args, **kwargs) self.model_name = model_name self.model_dir = model_dir - self.target_img_size = target_img_size self._use_cuda = _use_cuda def post_init(self): @@ -68,7 +66,7 @@ def apply(self, doc: 'gnes_pb2.Document'): for ci, ele in enumerate(zip(chunks, weight)): c = doc.chunks.add() c.doc_id = doc.doc_id - c.blob.CopyFrom(array2blob(self._crop_image_reshape(original_image, ele[0]))) + c.blob.CopyFrom(array2blob(self._crop_resize(original_image, ele[0]))) c.offset_1d = ci c.offset_nd.x.extend(self._get_seg_offset_nd(all_subareas, index, ele[0])) c.weight = self._cal_area(ele[0]) / (original_image.size[0] * original_image.size[1]) @@ -83,7 +81,7 @@ def apply(self, doc: 'gnes_pb2.Document'): else: self.logger.error('bad document: "raw_bytes" is empty!') - def _crop_image_reshape(self, original_image, coordinates): + def _crop_resize(self, original_image, coordinates): return np.array(original_image.crop(coordinates).resize((self.target_img_size, self.target_img_size))) diff --git a/gnes/preprocessor/image/sliding_window.py b/gnes/preprocessor/image/sliding_window.py index bef92f53..40635531 100644 --- a/gnes/preprocessor/image/sliding_window.py +++ b/gnes/preprocessor/image/sliding_window.py @@ -29,13 +29,11 @@ class BaseSlidingPreprocessor(BaseImagePreprocessor): def __init__(self, window_size: int = 64, stride_height: int = 64, stride_wide: int = 64, - target_img_size: int = 224, *args, **kwargs): super().__init__(*args, **kwargs) self.window_size = window_size self.stride_height = stride_height self.stride_wide = stride_wide - self.target_img_size = target_img_size def apply(self, doc: 'gnes_pb2.Document'): super().apply(doc) diff --git a/tests/imgs/test.zip b/tests/imgs/test.zip index d4cb17d894e49d5041458fd78b40e9601d1d87fd..417fec0b10977f6d85fb714f595ff3fb9f6d1a94 100644 GIT binary patch delta 24233 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 diff --git a/tests/test_image_preprocessor.py b/tests/test_image_preprocessor.py index c224a462..e7286567 100644 --- a/tests/test_image_preprocessor.py +++ b/tests/test_image_preprocessor.py @@ -15,6 +15,7 @@ def setUp(self): self.unary_img_pre_yaml = os.path.join(self.dirname, 'yaml', 'base-unary-image-prep.yml') self.slidingwindow_img_pre_yaml = os.path.join(self.dirname, 'yaml', 'base-vanilla_sldwin-image-prep.yml') self.segmentation_img_pre_yaml = os.path.join(self.dirname, 'yaml', 'base-segmentation-image-prep.yml') + self.resize_img_pre_yaml = os.path.join(self.dirname, 'yaml', 'resize-image-prep.yml') def test_unary_preprocessor_service_empty(self): args = set_preprocessor_service_parser().parse_args([ @@ -116,9 +117,31 @@ def test_unary_preprocessor_service_realdata(self): self.assertEqual(len(d.chunks), 1) self.assertEqual(len(blob2array(d.chunks[0].blob).shape), 3) self.assertEqual(blob2array(d.chunks[0].blob).shape[-1], 3) + + def test_resize_preprocessor_service_realdata(self): + args = set_preprocessor_service_parser().parse_args([ + '--yaml_path', self.resize_img_pre_yaml + ]) + c_args = _set_client_parser().parse_args([ + '--port_in', str(args.port_out), + '--port_out', str(args.port_in) + ]) + all_zips = zipfile.ZipFile(os.path.join(self.dirname, 'imgs/test.zip')) + all_bytes = [all_zips.open(v).read() for v in all_zips.namelist()] + + with PreprocessorService(args), ZmqClient(c_args) as client: + for req in RequestGenerator.index(all_bytes): + msg = gnes_pb2.Message() + msg.request.index.CopyFrom(req.index) + client.send_message(msg) + r = client.recv_message() + self.assertEqual(r.envelope.routes[0].service, 'PreprocessorService:PipelinePreprocessor') + for d in r.request.index.docs: + self.assertEqual(len(d.chunks), 1) + self.assertEqual(len(blob2array(d.chunks[0].blob).shape), 3) + self.assertEqual(blob2array(d.chunks[0].blob).shape[-1], 3) self.assertEqual(blob2array(d.chunks[0].blob).shape[0], 224) self.assertEqual(blob2array(d.chunks[0].blob).shape[1], 224) - print(blob2array(d.chunks[0].blob).dtype) def test_slidingwindow_preprocessor_service_realdata(self): args = set_preprocessor_service_parser().parse_args([ @@ -144,7 +167,6 @@ def test_slidingwindow_preprocessor_service_realdata(self): self.assertEqual(blob2array(d.chunks[0].blob).shape[-1], 3) self.assertEqual(blob2array(d.chunks[0].blob).shape[0], 224) self.assertEqual(blob2array(d.chunks[0].blob).shape[1], 224) - print(blob2array(d.chunks[0].blob).dtype) def test_segmentation_preprocessor_service_realdata(self): args = set_preprocessor_service_parser().parse_args([ @@ -170,4 +192,4 @@ def test_segmentation_preprocessor_service_realdata(self): self.assertEqual(blob2array(d.chunks[0].blob).shape[-1], 3) self.assertEqual(blob2array(d.chunks[0].blob).shape[0], 224) self.assertEqual(blob2array(d.chunks[0].blob).shape[1], 224) - print(blob2array(d.chunks[0].blob).dtype) \ No newline at end of file + print(blob2array(d.chunks[0].blob).dtype) diff --git a/tests/yaml/resize-image-prep.yml b/tests/yaml/resize-image-prep.yml new file mode 100644 index 00000000..b1015b8d --- /dev/null +++ b/tests/yaml/resize-image-prep.yml @@ -0,0 +1,9 @@ +!PipelinePreprocessor +component: + - !BaseUnaryPreprocessor + parameter: + doc_type: 2 + - !ResizeChunkPreprocessor + parameter: + target_height: 224 + target_width: 224 \ No newline at end of file diff --git a/yaml-example/component/img_preprocessor_singleton.yml b/yaml-example/component/img_preprocessor_unary.yml similarity index 100% rename from yaml-example/component/img_preprocessor_singleton.yml rename to yaml-example/component/img_preprocessor_unary.yml From d0b2ef0b1c8d781e558d1a7f0d7708c355f09d37 Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Thu, 1 Aug 2019 16:34:58 +0800 Subject: [PATCH 03/11] feat(composer): more interaction for gnes board --- gnes/cli/parser.py | 1 - gnes/composer/base.py | 10 ++++---- gnes/composer/flask.py | 19 +++++++------- gnes/resources/compose/gnes-board.html | 35 ++++++++++++++++++-------- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/gnes/cli/parser.py b/gnes/cli/parser.py index 7dab3078..d5325ada 100644 --- a/gnes/cli/parser.py +++ b/gnes/cli/parser.py @@ -54,7 +54,6 @@ def set_composer_parser(parser=None): 'gnes', '/'.join(('resources', 'config', 'compose', 'default.yml'))), help='yaml config of the service') parser.add_argument('--html_path', type=argparse.FileType('w', encoding='utf8'), - default='./gnes-board.html', help='output path of the HTML file, will contain all possible generations') parser.add_argument('--shell_path', type=argparse.FileType('w', encoding='utf8'), help='output path of the shell-based starting script') diff --git a/gnes/composer/base.py b/gnes/composer/base.py index 41e77f23..3534e75d 100644 --- a/gnes/composer/base.py +++ b/gnes/composer/base.py @@ -292,11 +292,11 @@ def build_mermaid(all_layers: List['YamlComposer.Layer'], mermaid_leftright: boo # if len(last_layer.components) > 1: # self.mermaid_graph.append('\tend') - style = ['classDef gRPCFrontendCLS fill:#FFAA04,stroke:#277CE8,stroke-width:1px;', - 'classDef EncoderCLS fill:#27E1E8,stroke:#277CE8,stroke-width:1px;', - 'classDef IndexerCLS fill:#27E1E8,stroke:#277CE8,stroke-width:1px;', - 'classDef RouterCLS fill:#2BFFCB,stroke:#277CE8,stroke-width:1px;', - 'classDef PreprocessorCLS fill:#27E1E8,stroke:#277CE8,stroke-width:1px;'] + style = ['classDef gRPCFrontendCLS fill:#FFE0E0,stroke:#FFE0E0,stroke-width:1px;', + 'classDef EncoderCLS fill:#FFDAAF,stroke:#FFDAAF,stroke-width:1px;', + 'classDef IndexerCLS fill:#FFFBC1,stroke:#FFFBC1,stroke-width:1px;', + 'classDef RouterCLS fill:#C9E8D2,stroke:#C9E8D2,stroke-width:1px;', + 'classDef PreprocessorCLS fill:#CEEEEF,stroke:#CEEEEF,stroke-width:1px;'] class_def = ['class %s %s;' % (','.join(v), k) for k, v in cls_dict.items()] mermaid_str = '\n'.join( ['graph %s' % ('LR' if mermaid_leftright else 'TD')] + mermaid_graph + style + class_def) diff --git a/gnes/composer/flask.py b/gnes/composer/flask.py index 36003967..fe132c11 100644 --- a/gnes/composer/flask.py +++ b/gnes/composer/flask.py @@ -12,8 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -import tempfile +import io from .base import YamlComposer from ..cli.parser import set_composer_parser @@ -37,23 +36,25 @@ def _create_flask_app(self): # support up to 10 concurrent HTTP requests app = Flask(__name__) + args = set_composer_parser().parse_args([]) + default_html = YamlComposer(args).build_all()['html'] @app.route('/', methods=['GET']) def _get_homepage(): - return YamlComposer(set_composer_parser().parse_args([])).build_all()['html'] + return default_html @app.route('/generate', methods=['POST']) def _regenerate(): data = request.form if request.form else request.json if not data or 'yaml-config' not in data: return '

Bad POST request

your POST request does not contain "yaml-config" field!', 406 - f = tempfile.NamedTemporaryFile('w', delete=False).name - with open(f, 'w', encoding='utf8') as fp: - fp.write(data['yaml-config']) try: - return YamlComposer(set_composer_parser().parse_args([ - '--yaml_path', f - ])).build_all()['html'] + args.yaml_path = io.StringIO(data['yaml-config']) + if data.get('mermaid_direction', 'top-down').lower() == 'left-right': + args.mermaid_leftright = True + if 'docker-image' in data: + args.docker_img = data['docker-image'] + return YamlComposer(args).build_all()['html'] except Exception as e: self.logger.error(e) return '

Bad YAML input

please kindly check the format, indent and content of your YAML file!', 400 diff --git a/gnes/resources/compose/gnes-board.html b/gnes/resources/compose/gnes-board.html index 4a0f8406..e054a9ad 100644 --- a/gnes/resources/compose/gnes-board.html +++ b/gnes/resources/compose/gnes-board.html @@ -123,18 +123,22 @@ } GNES Board - + - + - + - + @@ -215,25 +219,36 @@ YAML config
-
+
- +
- - -
+
+
+ + +
+
+ + +
From 5315c0c9c12cee0879a2fae7e208853d0dbcf5c5 Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Thu, 1 Aug 2019 17:11:02 +0800 Subject: [PATCH 04/11] fix(test): fix test images by removing mac stuff --- tests/imgs/test.zip | Bin 39433 -> 38539 bytes tests/test_image_encoder.py | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/imgs/test.zip b/tests/imgs/test.zip index 417fec0b10977f6d85fb714f595ff3fb9f6d1a94..c284901fee6259919b9727b9d73382ec032314db 100644 GIT binary patch delta 23332 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 diff --git a/tests/test_image_encoder.py b/tests/test_image_encoder.py index cd17aaec..5582d039 100644 --- a/tests/test_image_encoder.py +++ b/tests/test_image_encoder.py @@ -5,12 +5,13 @@ from gnes.encoder.image.base import BasePytorchEncoder from gnes.preprocessor.base import BaseUnaryPreprocessor +from gnes.preprocessor.image.resize import ResizeChunkPreprocessor from gnes.preprocessor.image.sliding_window import VanillaSlidingPreprocessor from gnes.proto import gnes_pb2, blob2array def img_process_for_test(dirname): - zipfile_ = zipfile.ZipFile(os.path.join(dirname, 'imgs/test.zip'), "r") + zipfile_ = zipfile.ZipFile(os.path.join(dirname, 'imgs/test.zip')) all_bytes = [zipfile_.open(v).read() for v in zipfile_.namelist()] test_img = [] for raw_bytes in all_bytes: @@ -20,6 +21,7 @@ def img_process_for_test(dirname): test_img_all_preprocessor = [] for preprocessor in [BaseUnaryPreprocessor(doc_type=gnes_pb2.Document.IMAGE), + ResizeChunkPreprocessor(), VanillaSlidingPreprocessor()]: test_img_copy = copy.deepcopy(test_img) for img in test_img_copy: From 14cdfabed5bb1ac8e75f1944f31323c69df6d9d8 Mon Sep 17 00:00:00 2001 From: Jem Date: Thu, 1 Aug 2019 18:41:20 +0800 Subject: [PATCH 05/11] fix(sliding window): fix the boundary --- gnes/preprocessor/image/sliding_window.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gnes/preprocessor/image/sliding_window.py b/gnes/preprocessor/image/sliding_window.py index 40635531..63cadeaf 100644 --- a/gnes/preprocessor/image/sliding_window.py +++ b/gnes/preprocessor/image/sliding_window.py @@ -98,7 +98,7 @@ def _get_slid_offset_nd(self, all_subareas: List[List[int]], index: List[List[in @staticmethod def _get_location(all_subareas: List[List[int]], center_point: List[float]) -> List[bool]: location_list = [] - x_boundary = max([x[1] for x in all_subareas]) + x_boundary = max([x[2] for x in all_subareas]) y_boundary = max([y[3] for y in all_subareas]) for area in all_subareas: if center_point[0] in range(int(area[0]), int(area[2])) and center_point[1] in range(int(area[1]), @@ -110,6 +110,8 @@ def _get_location(all_subareas: List[List[int]], center_point: List[float]) -> L location_list.append(True) else: location_list.append(False) + if True not in location_list: + location_list[-1] = True return location_list From 54a931c78345617014229792d9e9ac6ca6ae4f71 Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Fri, 2 Aug 2019 11:13:03 +0800 Subject: [PATCH 06/11] fix(test): fix test images by removing mac stuff --- tests/test_stream_grpc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_stream_grpc.py b/tests/test_stream_grpc.py index 1278534c..ad80ae1d 100644 --- a/tests/test_stream_grpc.py +++ b/tests/test_stream_grpc.py @@ -38,7 +38,7 @@ def setUp(self): self.all_bytes = [b'abc', b'def', b'cde'] * 10 self.all_bytes2 = [b'abc', b'def', b'cde'] - @unittest.mock.patch.dict(os.environ, {'http_proxy': '', 'https_proxy': ''}) + # @unittest.mock.patch.dict(os.environ, {'http_proxy': '', 'https_proxy': ''}) def test_grpc_frontend(self): args = set_grpc_frontend_parser().parse_args([ '--grpc_host', '127.0.0.1', @@ -61,7 +61,7 @@ def test_grpc_frontend(self): self.assertEqual(resp.request_id, str(len(self.all_bytes))) # idx start with 0, but +1 for final FLUSH - @unittest.mock.patch.dict(os.environ, {'http_proxy': '', 'https_proxy': ''}) + # @unittest.mock.patch.dict(os.environ, {'http_proxy': '', 'https_proxy': ''}) def test_async_block(self): args = set_grpc_frontend_parser().parse_args([ '--grpc_host', '127.0.0.1', From 50fdc0414659d7fe0acf858fe23e67c1be1bee0b Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Fri, 2 Aug 2019 11:45:20 +0800 Subject: [PATCH 07/11] fix(base): fix ref to CompositionalTrainableBase --- gnes/base/__init__.py | 4 ++-- gnes/cli/parser.py | 5 +++-- gnes/encoder/text/bert.py | 4 ++-- gnes/indexer/base.py | 5 ++--- tests/test_parser.py | 11 +++++++++++ 5 files changed, 20 insertions(+), 9 deletions(-) create mode 100644 tests/test_parser.py diff --git a/gnes/base/__init__.py b/gnes/base/__init__.py index 9a6d70b9..5945fd23 100644 --- a/gnes/base/__init__.py +++ b/gnes/base/__init__.py @@ -295,7 +295,7 @@ def _get_instance_from_yaml(cls, constructor, node, stop_on_import_error=False): if stop_on_import_error: raise RuntimeError('Cannot import module, pip install may required') from ex - if node.tag in {'!PipelineEncoder', '!CompositionalEncoder'}: + if node.tag in {'!PipelineEncoder', '!CompositionalTrainableBase'}: os.environ['GNES_WARN_UNNAMED_COMPONENT'] = '0' data = ruamel.yaml.constructor.SafeConstructor.construct_mapping( @@ -325,7 +325,7 @@ def _get_instance_from_yaml(cls, constructor, node, stop_on_import_error=False): obj.logger.info('initialize %s from a yaml config' % cls.__name__) cls.init_from_yaml = False - if node.tag in {'!PipelineEncoder', '!CompositionalEncoder'}: + if node.tag in {'!PipelineEncoder', '!CompositionalTrainableBase'}: os.environ['GNES_WARN_UNNAMED_COMPONENT'] = '1' return obj, data, load_from_dump diff --git a/gnes/cli/parser.py b/gnes/cli/parser.py index d5325ada..154b70e8 100644 --- a/gnes/cli/parser.py +++ b/gnes/cli/parser.py @@ -213,10 +213,11 @@ def set_grpc_frontend_parser(parser=None): from ..service.base import SocketType if not parser: parser = set_base_parser() - _set_client_parser(parser) + set_service_parser(parser) _set_grpc_parser(parser) parser.set_defaults(socket_in=SocketType.PULL_BIND, - socket_out=SocketType.PUSH_BIND) + socket_out=SocketType.PUSH_BIND, + read_only=True) parser.add_argument('--max_concurrency', type=int, default=10, help='maximum concurrent client allowed') parser.add_argument('--max_send_size', type=int, default=100, diff --git a/gnes/encoder/text/bert.py b/gnes/encoder/text/bert.py index 0bb1997f..9003a25d 100644 --- a/gnes/encoder/text/bert.py +++ b/gnes/encoder/text/bert.py @@ -20,7 +20,7 @@ import numpy as np -from ..base import CompositionalEncoder, BaseTextEncoder +from ..base import CompositionalTrainableBase, BaseTextEncoder from ...helper import batching @@ -45,7 +45,7 @@ def close(self): self.bc_encoder.close() -class BertEncoderWithServer(CompositionalEncoder): +class BertEncoderWithServer(CompositionalTrainableBase): def encode(self, text: List[str], *args, **kwargs) -> np.ndarray: return self.component['bert_client'].encode(text, *args, **kwargs) diff --git a/gnes/indexer/base.py b/gnes/indexer/base.py index 57d95c8e..91cdd613 100644 --- a/gnes/indexer/base.py +++ b/gnes/indexer/base.py @@ -19,8 +19,7 @@ import numpy as np -from ..base import TrainableBase -from ..encoder.base import CompositionalEncoder +from ..base import TrainableBase, CompositionalTrainableBase class BaseIndexer(TrainableBase): @@ -71,7 +70,7 @@ def normalize_score(self, *args, **kwargs): pass -class JointIndexer(CompositionalEncoder): +class JointIndexer(CompositionalTrainableBase): @property def component(self): diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 00000000..38c54a30 --- /dev/null +++ b/tests/test_parser.py @@ -0,0 +1,11 @@ +import unittest + +from gnes.cli.parser import set_grpc_frontend_parser + + +class TestParser(unittest.TestCase): + def test_service_parser(self): + args1 = set_grpc_frontend_parser().parse_args([]) + args2 = set_grpc_frontend_parser().parse_args([]) + self.assertNotEqual(args1.port_in, args2.port_in) + self.assertNotEqual(args1.port_out, args2.port_out) From f6a801f7b18a5df435335778deaba790df09526c Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Fri, 2 Aug 2019 12:17:07 +0800 Subject: [PATCH 08/11] fix(test): fix preprocessor building for image test --- gnes/composer/base.py | 6 +++--- tests/test_image_encoder.py | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/gnes/composer/base.py b/gnes/composer/base.py index 3534e75d..1fcac2ef 100644 --- a/gnes/composer/base.py +++ b/gnes/composer/base.py @@ -156,10 +156,10 @@ def build_layers(self) -> List['YamlComposer.Layer']: last_layer = self._layers[idx - 1] for l in self._add_router(last_layer, layer): all_layers.append(copy.deepcopy(l)) - # # add frontend - # for l in self._add_router(all_layers[-1], all_layers[0]): - # all_layers.append(copy.deepcopy(l)) all_layers[0] = copy.deepcopy(self._layers[0]) + + # gRPCfrontend should always on the bind role + return all_layers @staticmethod diff --git a/tests/test_image_encoder.py b/tests/test_image_encoder.py index 5582d039..82917678 100644 --- a/tests/test_image_encoder.py +++ b/tests/test_image_encoder.py @@ -4,7 +4,7 @@ import zipfile from gnes.encoder.image.base import BasePytorchEncoder -from gnes.preprocessor.base import BaseUnaryPreprocessor +from gnes.preprocessor.base import BaseUnaryPreprocessor, PipelinePreprocessor from gnes.preprocessor.image.resize import ResizeChunkPreprocessor from gnes.preprocessor.image.sliding_window import VanillaSlidingPreprocessor from gnes.proto import gnes_pb2, blob2array @@ -20,8 +20,10 @@ def img_process_for_test(dirname): test_img.append(d) test_img_all_preprocessor = [] - for preprocessor in [BaseUnaryPreprocessor(doc_type=gnes_pb2.Document.IMAGE), - ResizeChunkPreprocessor(), + pipline_prep1 = PipelinePreprocessor() + pipline_prep1.component = lambda: [BaseUnaryPreprocessor(doc_type=gnes_pb2.Document.IMAGE), + ResizeChunkPreprocessor()] + for preprocessor in [pipline_prep1, VanillaSlidingPreprocessor()]: test_img_copy = copy.deepcopy(test_img) for img in test_img_copy: From f92dad546baed6d66d45bfe6489ace513b7508f0 Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Fri, 2 Aug 2019 12:31:45 +0800 Subject: [PATCH 09/11] fix(test): fix grpc gentle shutdown --- gnes/composer/base.py | 16 +++++++++++++++- gnes/service/grpc.py | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/gnes/composer/base.py b/gnes/composer/base.py index 1fcac2ef..76e524a7 100644 --- a/gnes/composer/base.py +++ b/gnes/composer/base.py @@ -159,7 +159,21 @@ def build_layers(self) -> List['YamlComposer.Layer']: all_layers[0] = copy.deepcopy(self._layers[0]) # gRPCfrontend should always on the bind role - + assert all_layers[0].is_single_component + assert all_layers[0].components[0].name == 'gRPCFrontend' + + if all_layers[0].components[0]['socket_in'] == str(SocketType.SUB_CONNECT): + # change to sub bind + all_layers[0].components[0]['socket_in'] = str(SocketType.SUB_BIND) + for c in all_layers[-1].components: + c['socket_out'] = str(SocketType.PUB_CONNECT) + + if all_layers[0].components[0]['socket_in'] == str(SocketType.PULL_CONNECT): + # change to sub bind + all_layers[0].components[0]['socket_in'] = str(SocketType.PULL_BIND) + for c in all_layers[-1].components: + c['socket_out'] = str(SocketType.PUSH_CONNECT) + return all_layers @staticmethod diff --git a/gnes/service/grpc.py b/gnes/service/grpc.py index 4745e0b8..231f8e17 100644 --- a/gnes/service/grpc.py +++ b/gnes/service/grpc.py @@ -106,7 +106,7 @@ def add_envelope(self, body: 'gnes_pb2.Request', zmq_client: 'ZmqClient'): msg.envelope.num_part.append(1) msg.envelope.timeout = 5000 r = msg.envelope.routes.add() - r.service = zmq_client.__class__.__name__ + r.service = GRPCFrontend.__name__ r.timestamp.GetCurrentTime() msg.request.CopyFrom(body) return msg @@ -161,4 +161,4 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): - pass + self.server.stop() From c5c811d4ce761ec9ea6d78220d6f49605e7dd2f7 Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Fri, 2 Aug 2019 13:03:27 +0800 Subject: [PATCH 10/11] fix(test): fix grpc gentle shutdown --- gnes/helper.py | 2 +- gnes/service/base.py | 2 +- gnes/service/grpc.py | 2 +- tests/test_stream_grpc.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gnes/helper.py b/gnes/helper.py index 7e69b0bf..f732ca68 100644 --- a/gnes/helper.py +++ b/gnes/helper.py @@ -194,6 +194,7 @@ class ColoredFormatter(Formatter): 'WARNING': dict(color='red', on_color='on_yellow'), # yellow 'ERROR': dict(color='white', on_color='on_red'), # 31 for red 'CRITICAL': dict(color='red', on_color='on_white'), # white on red bg + 'SUCCESS': dict(color='white', on_color='on_green'), # green } PREFIX = '\033[' @@ -535,4 +536,3 @@ def load_contrib_module(): profiling = time_profile yaml = _get_yaml() - diff --git a/gnes/service/base.py b/gnes/service/base.py index bfee42d2..be9972cc 100644 --- a/gnes/service/base.py +++ b/gnes/service/base.py @@ -264,7 +264,7 @@ def _run(self, ctx): self.is_ready.set() self.is_event_loop.set() self._start_auto_dump() - self.logger.info('ready and listening') + self.logger.info(colored('ready and listening', color='white', on_color='on_green')) while self.is_event_loop.is_set(): pull_sock = None socks = dict(poller.poll()) diff --git a/gnes/service/grpc.py b/gnes/service/grpc.py index 231f8e17..f89fdf22 100644 --- a/gnes/service/grpc.py +++ b/gnes/service/grpc.py @@ -161,4 +161,4 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): - self.server.stop() + self.server.stop(None) diff --git a/tests/test_stream_grpc.py b/tests/test_stream_grpc.py index ad80ae1d..f500fd20 100644 --- a/tests/test_stream_grpc.py +++ b/tests/test_stream_grpc.py @@ -81,7 +81,7 @@ def test_async_block(self): '--socket_out', str(SocketType.PUSH_CONNECT), ]) - with Router1(p1_args), Router2(p2_args), GRPCFrontend(args), grpc.insecure_channel( + with GRPCFrontend(args), Router1(p1_args), Router2(p2_args), grpc.insecure_channel( '%s:%s' % (args.grpc_host, args.grpc_port), options=[('grpc.max_send_message_length', 70 * 1024 * 1024), ('grpc.max_receive_message_length', 70 * 1024 * 1024)]) as channel: From 823bdeda9ead30ae989a8c46cb32a51581bc4753 Mon Sep 17 00:00:00 2001 From: hanhxiao Date: Fri, 2 Aug 2019 13:10:56 +0800 Subject: [PATCH 11/11] fix(test): fix grpc gentle shutdown --- gnes/composer/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnes/composer/base.py b/gnes/composer/base.py index 76e524a7..e6fe7137 100644 --- a/gnes/composer/base.py +++ b/gnes/composer/base.py @@ -160,7 +160,7 @@ def build_layers(self) -> List['YamlComposer.Layer']: # gRPCfrontend should always on the bind role assert all_layers[0].is_single_component - assert all_layers[0].components[0].name == 'gRPCFrontend' + assert all_layers[0].components[0]['name'] == 'gRPCFrontend' if all_layers[0].components[0]['socket_in'] == str(SocketType.SUB_CONNECT): # change to sub bind @@ -173,7 +173,7 @@ def build_layers(self) -> List['YamlComposer.Layer']: all_layers[0].components[0]['socket_in'] = str(SocketType.PULL_BIND) for c in all_layers[-1].components: c['socket_out'] = str(SocketType.PUSH_CONNECT) - + return all_layers @staticmethod