diff --git a/projects/opendr_ws_2/src/data_generation/data_generation/__init__.py b/projects/opendr_ws_2/src/data_generation/data_generation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/projects/opendr_ws_2/src/data_generation/data_generation/synthetic_facial_generation.py b/projects/opendr_ws_2/src/data_generation/data_generation/synthetic_facial_generation.py new file mode 100644 index 0000000000..a32b925c03 --- /dev/null +++ b/projects/opendr_ws_2/src/data_generation/data_generation/synthetic_facial_generation.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3.6 +# Copyright 2020-2022 OpenDR European Project +# +# 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 cv2 +import os +import argparse +import numpy as np + +import rclpy +from rclpy.node import Node + +from sensor_msgs.msg import Image as ROS_Image +from cv_bridge import CvBridge + +from opendr.projects.python.simulation.synthetic_multi_view_facial_image_generation.algorithm.DDFA.utils.ddfa \ + import str2bool +from opendr.src.opendr.engine.data import Image +from opendr.projects.python.simulation.synthetic_multi_view_facial_image_generation.SyntheticDataGeneration \ + import MultiviewDataGeneration + + +class SyntheticDataGenerator(Node): + + def __init__(self, args, input_rgb_image_topic="/image_raw", + output_rgb_image_topic="/opendr/synthetic_facial_images"): + """ + Creates a ROS Node for SyntheticDataGeneration + :param input_rgb_image_topic: Topic from which we are reading the input image + :type input_rgb_image_topic: str + :param output_rgb_image_topic: Topic to which we are publishing the synthetic facial image (if None, no image + is published) + :type output_rgb_image_topic: str + """ + super().__init__('synthetic_facial_image_generation_node') + self.image_publisher = self.create_publisher(ROS_Image, output_rgb_image_topic, 10) + self.create_subscription(ROS_Image, input_rgb_image_topic, self.callback, 1) + self._cv_bridge = CvBridge() + self.ID = 0 + self.args = args + self.path_in = args.path_in + self.key = str(args.path_3ddfa + "/example/Images/") + self.key1 = str(args.path_3ddfa + "/example/") + self.key2 = str(args.path_3ddfa + "/results/") + self.save_path = args.save_path + self.val_yaw = args.val_yaw + self.val_pitch = args.val_pitch + self.device = args.device + + # Initialize the SyntheticDataGeneration + self.synthetic = MultiviewDataGeneration(self.args) + + def callback(self, data): + """ + Callback that process the input data and publishes to the corresponding topics + :param data: input message + :type data: sensor_msgs.msg.Image + """ + + # Convert sensor_msgs.msg.Image into OpenDR Image + + cv_image = self._cv_bridge.imgmsg_to_cv2(data, desired_encoding="rgb8") + image = Image(np.asarray(cv_image, dtype=np.uint8)) + self.ID = self.ID + 1 + # Get an OpenCV image back + image = cv2.cvtColor(image.opencv(), cv2.COLOR_RGBA2BGR) + name = str(f"{self.ID:02d}" + "_single.jpg") + cv2.imwrite(os.path.join(self.path_in, name), image) + + if self.ID == 10: + # Run SyntheticDataGeneration + self.synthetic.eval() + self.ID = 0 + # Annotate image and publish results + current_directory_path = os.path.join(self.save_path, str("/Documents_orig/")) + for file in os.listdir(current_directory_path): + name, ext = os.path.splitext(file) + if ext == ".jpg": + image_file_savepath = os.path.join(current_directory_path, file) + cv_image = cv2.imread(image_file_savepath) + cv_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB) + if self.image_publisher is not None: + image = Image(np.array(cv_image, dtype=np.uint8)) + message = self.bridge.to_ros_image(image, encoding="rgb8") + self.image_publisher.publish(message) + for f in os.listdir(self.path_in): + os.remove(os.path.join(self.path_in, f)) + + +def main(args=None): + rclpy.init(args=args) + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--input_rgb_image_topic", help="Topic name for input rgb image", + type=str, default="/image_raw") + parser.add_argument("-o", "--output_rgb_image_topic", help="Topic name for output annotated rgb image", + type=str, default="/opendr/synthetic_facial_images") + parser.add_argument("--path_in", default=os.path.join("opendr", "projects", + "data_generation", + "synthetic_multi_view_facial_image_generation", + "demos", "imgs_input"), + type=str, help='Give the path of image folder') + parser.add_argument('--path_3ddfa', default=os.path.join("opendr", "projects", + "data_generation", + "synthetic_multi_view_facial_image_generation", + "algorithm", "DDFA"), + type=str, help='Give the path of DDFA folder') + parser.add_argument('--save_path', default=os.path.join("opendr", "projects", + "data_generation", + "synthetic_multi_view_facial_image_generation", + "results"), + type=str, help='Give the path of results folder') + parser.add_argument('--val_yaw', default="10 20", nargs='+', type=str, help='yaw poses list between [-90,90]') + parser.add_argument('--val_pitch', default="30 40", nargs='+', type=str, help='pitch poses list between [-90,90]') + parser.add_argument("--device", default="cuda", type=str, help="choose between cuda or cpu ") + parser.add_argument('-f', '--files', nargs='+', + help='image files paths fed into network, single or multiple images') + parser.add_argument('--show_flg', default='false', type=str2bool, help='whether show the visualization result') + parser.add_argument('--dump_res', default='true', type=str2bool, + help='whether write out the visualization image') + parser.add_argument('--dump_vertex', default='false', type=str2bool, + help='whether write out the dense face vertices to mat') + parser.add_argument('--dump_ply', default='true', type=str2bool) + parser.add_argument('--dump_pts', default='true', type=str2bool) + parser.add_argument('--dump_roi_box', default='false', type=str2bool) + parser.add_argument('--dump_pose', default='true', type=str2bool) + parser.add_argument('--dump_depth', default='true', type=str2bool) + parser.add_argument('--dump_pncc', default='true', type=str2bool) + parser.add_argument('--dump_paf', default='true', type=str2bool) + parser.add_argument('--paf_size', default=3, type=int, help='PAF feature kernel size') + parser.add_argument('--dump_obj', default='true', type=str2bool) + parser.add_argument('--dlib_bbox', default='true', type=str2bool, help='whether use dlib to predict bbox') + parser.add_argument('--dlib_landmark', default='true', type=str2bool, + help='whether use dlib landmark to crop image') + parser.add_argument('-m', '--mode', default='gpu', type=str, help='gpu or cpu mode') + parser.add_argument('--bbox_init', default='two', type=str, + help='one|two: one-step bbox initialization or two-step') + parser.add_argument('--dump_2d_img', default='true', type=str2bool, help='whether to save 3d rendered image') + parser.add_argument('--dump_param', default='true', type=str2bool, help='whether to save param') + parser.add_argument('--dump_lmk', default='true', type=str2bool, help='whether to save landmarks') + parser.add_argument('--save_dir', default='./algorithm/DDFA/results', type=str, help='dir to save result') + parser.add_argument('--save_lmk_dir', default='./example', type=str, help='dir to save landmark result') + parser.add_argument('--img_list', default='./txt_name_batch.txt', type=str, help='test image list file') + parser.add_argument('--rank', default=0, type=int, help='used when parallel run') + parser.add_argument('--world_size', default=1, type=int, help='used when parallel run') + parser.add_argument('--resume_idx', default=0, type=int) + args = parser.parse_args() + + synthetic_data_generation_node = SyntheticDataGenerator(args=args, + input_rgb_image_topic=args.input_rgb_image_topic, + output_rgb_image_topic=args.output_rgb_image_topic) + + rclpy.spin(synthetic_data_generation_node) + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + synthetic_data_generation_node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/projects/opendr_ws_2/src/data_generation/package.xml b/projects/opendr_ws_2/src/data_generation/package.xml new file mode 100644 index 0000000000..9a712ee877 --- /dev/null +++ b/projects/opendr_ws_2/src/data_generation/package.xml @@ -0,0 +1,26 @@ + + + + data_generation + 1.0.0 + OpenDR ROS2 node for synthetic multiview facial image generation + tefas + Apache License v2.0 + + rclcpp> + sensor_msgs + + rclpy + opendr_ros2_bridge + + ament_cmake + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/projects/opendr_ws_2/src/data_generation/resource/data_generation b/projects/opendr_ws_2/src/data_generation/resource/data_generation new file mode 100644 index 0000000000..e69de29bb2 diff --git a/projects/opendr_ws_2/src/data_generation/setup.cfg b/projects/opendr_ws_2/src/data_generation/setup.cfg new file mode 100644 index 0000000000..6a735acf31 --- /dev/null +++ b/projects/opendr_ws_2/src/data_generation/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script-dir=$base/lib/data_generation +[install] +install-scripts=$base/lib/data_generation diff --git a/projects/opendr_ws_2/src/data_generation/setup.py b/projects/opendr_ws_2/src/data_generation/setup.py new file mode 100644 index 0000000000..b810963785 --- /dev/null +++ b/projects/opendr_ws_2/src/data_generation/setup.py @@ -0,0 +1,40 @@ +# Copyright 2020-2022 OpenDR European Project +# +# 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. + +from setuptools import setup + +package_name = 'data_generation' + +setup( + name=package_name, + version='1.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='OpenDR Project Coordinator', + maintainer_email='tefas@csd.auth.gr', + description='OpenDR ROS2 node for synthetic multiview facial image generation', + license='Apache License v2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'synthetic_facial_generation = data_generation.synthetic_facial_generation:main' + ], + }, +) diff --git a/projects/opendr_ws_2/src/data_generation/test/test_copyright.py b/projects/opendr_ws_2/src/data_generation/test/test_copyright.py new file mode 100644 index 0000000000..cc8ff03f79 --- /dev/null +++ b/projects/opendr_ws_2/src/data_generation/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/projects/opendr_ws_2/src/data_generation/test/test_flake8.py b/projects/opendr_ws_2/src/data_generation/test/test_flake8.py new file mode 100644 index 0000000000..18bd9331ea --- /dev/null +++ b/projects/opendr_ws_2/src/data_generation/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/projects/opendr_ws_2/src/data_generation/test/test_pep257.py b/projects/opendr_ws_2/src/data_generation/test/test_pep257.py new file mode 100644 index 0000000000..b234a3840f --- /dev/null +++ b/projects/opendr_ws_2/src/data_generation/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/projects/python/simulation/synthetic_multi_view_facial_image_generation/SyntheticDataGeneration.py b/projects/python/simulation/synthetic_multi_view_facial_image_generation/SyntheticDataGeneration.py index 4df8e55fff..59e114a64c 100644 --- a/projects/python/simulation/synthetic_multi_view_facial_image_generation/SyntheticDataGeneration.py +++ b/projects/python/simulation/synthetic_multi_view_facial_image_generation/SyntheticDataGeneration.py @@ -40,9 +40,9 @@ from shutil import copyfile import cv2 import os -from algorithm.DDFA import preprocessing_1 -from algorithm.DDFA import preprocessing_2 -from algorithm.Rotate_and_Render import test_multipose +from .algorithm.DDFA import preprocessing_1 +from .algorithm.DDFA import preprocessing_2 +from .algorithm.Rotate_and_Render import test_multipose class MultiviewDataGeneration(): diff --git a/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/models/networks/__init__.py b/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/models/networks/__init__.py index 91e0febc81..36314edf6e 100644 --- a/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/models/networks/__init__.py +++ b/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/models/networks/__init__.py @@ -1,18 +1,18 @@ import torch -from algorithm.Rotate_and_Render.models.networks.base_network import BaseNetwork -from algorithm.Rotate_and_Render.models.networks import loss -from algorithm.Rotate_and_Render.models.networks import discriminator -from algorithm.Rotate_and_Render.models.networks import generator -from algorithm.Rotate_and_Render.models.networks import encoder -from algorithm.Rotate_and_Render.models.networks.render import Render -import algorithm.Rotate_and_Render.util.util as util +from .base_network import BaseNetwork +from . import loss +from . import discriminator +from . import generator +from . import encoder +from .render import Render +from ...util.util import find_class_in_module __all__ = ['loss', 'discriminator', 'generator', 'encoder', 'Render'] def find_network_using_name(target_network_name, filename): target_class_name = target_network_name + filename module_name = 'algorithm.Rotate_and_Render.models.networks.' + filename - network = util.find_class_in_module(target_class_name, module_name) + network = find_class_in_module(target_class_name, module_name) assert issubclass(network, BaseNetwork), \ "Class %s should be a subclass of BaseNetwork" % network diff --git a/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/options/base_options.py b/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/options/base_options.py index 8528a58620..8bd14494e7 100644 --- a/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/options/base_options.py +++ b/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/options/base_options.py @@ -4,8 +4,8 @@ import os from ..util import util import torch -from algorithm.Rotate_and_Render import models -from algorithm.Rotate_and_Render import data +from .. import models +from .. import data import pickle __all__ = ['math'] diff --git a/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/test_multipose.py b/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/test_multipose.py index 8e8bf09b58..d18a1c48ba 100644 --- a/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/test_multipose.py +++ b/projects/python/simulation/synthetic_multi_view_facial_image_generation/algorithm/Rotate_and_Render/test_multipose.py @@ -14,7 +14,7 @@ import torch import math from .models.networks.rotate_render import TestRender -from algorithm.Rotate_and_Render.data import dataset_info +from .data import dataset_info multiprocessing.set_start_method('spawn', force=True) __all__ = ['dataset_info'] diff --git a/tests/test_license.py b/tests/test_license.py old mode 100755 new mode 100644 index 894d44602a..ece6d5bb35 --- a/tests/test_license.py +++ b/tests/test_license.py @@ -108,6 +108,7 @@ def setUp(self): 'src/opendr/perception/facial_expression_recognition/landmark_based_facial_expression_recognition', 'projects/opendr_ws_2/src/opendr_perception/test', 'projects/opendr_ws_2/src/opendr_ros2_bridge/test', + 'projects/opendr_ws_2/src/data_generation/test', 'projects/opendr_ws_2/src/opendr_simulation/test', ] @@ -123,6 +124,7 @@ def setUp(self): 'src/opendr/perception/multimodal_human_centric/audiovisual_emotion_learner/algorithm/utils.py', 'projects/opendr_ws_2/src/opendr_perception/setup.py', 'projects/opendr_ws_2/src/opendr_ros2_bridge/setup.py', + 'projects/opendr_ws_2/src/data_generation/setup.py', 'projects/opendr_ws_2/src/opendr_simulation/setup.py', ]