From c90651d226703446f8522fe655e8d06246a81d4b Mon Sep 17 00:00:00 2001 From: iory Date: Tue, 3 May 2022 02:03:56 +0900 Subject: [PATCH 01/45] Add jsk_rosbag_tools package --- jsk_common/package.xml | 1 + jsk_rosbag_tools/.gitignore | 2 + jsk_rosbag_tools/CMakeLists.txt | 73 +++++++ jsk_rosbag_tools/README.md | 155 +++++++++++++++ jsk_rosbag_tools/package.xml | 35 ++++ .../python/jsk_rosbag_tools/__init__.py | 5 + .../python/jsk_rosbag_tools/compress.py | 37 ++++ .../python/jsk_rosbag_tools/cv.py | 179 ++++++++++++++++++ .../jsk_rosbag_tools/cv_bridge_compat.py | 35 ++++ .../python/jsk_rosbag_tools/cv_compat.py | 14 ++ .../python/jsk_rosbag_tools/extract.py | 138 ++++++++++++++ .../python/jsk_rosbag_tools/info.py | 22 +++ .../python/jsk_rosbag_tools/merge.py | 83 ++++++++ .../python/jsk_rosbag_tools/video.py | 179 ++++++++++++++++++ jsk_rosbag_tools/requirements.in | 4 + jsk_rosbag_tools/samples/data/.gitignore | 2 + jsk_rosbag_tools/scripts/bag_to_video.py | 116 ++++++++++++ jsk_rosbag_tools/scripts/compress_imgs.py | 48 +++++ jsk_rosbag_tools/scripts/merge.py | 37 ++++ jsk_rosbag_tools/scripts/tf_static_to_tf.py | 54 ++++++ jsk_rosbag_tools/scripts/video_to_bag.py | 43 +++++ jsk_rosbag_tools/setup.py | 12 ++ jsk_rosbag_tools/tests/output/.gitignore | 2 + .../tests/test_jsk_rosbag_tools.py | 98 ++++++++++ .../tests/test_jsk_rosbag_tools.test | 7 + 25 files changed, 1381 insertions(+) create mode 100644 jsk_rosbag_tools/.gitignore create mode 100644 jsk_rosbag_tools/CMakeLists.txt create mode 100644 jsk_rosbag_tools/README.md create mode 100644 jsk_rosbag_tools/package.xml create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/__init__.py create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/compress.py create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/cv_bridge_compat.py create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/cv_compat.py create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/info.py create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/merge.py create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/video.py create mode 100644 jsk_rosbag_tools/requirements.in create mode 100644 jsk_rosbag_tools/samples/data/.gitignore create mode 100644 jsk_rosbag_tools/scripts/bag_to_video.py create mode 100644 jsk_rosbag_tools/scripts/compress_imgs.py create mode 100644 jsk_rosbag_tools/scripts/merge.py create mode 100644 jsk_rosbag_tools/scripts/tf_static_to_tf.py create mode 100644 jsk_rosbag_tools/scripts/video_to_bag.py create mode 100644 jsk_rosbag_tools/setup.py create mode 100644 jsk_rosbag_tools/tests/output/.gitignore create mode 100644 jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py create mode 100644 jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test diff --git a/jsk_common/package.xml b/jsk_common/package.xml index 326fa7e55..2a643899e 100644 --- a/jsk_common/package.xml +++ b/jsk_common/package.xml @@ -19,6 +19,7 @@ audio_video_recorder dynamic_tf_publisher image_view2 + jsk_rosbag_tools jsk_topic_tools jsk_tools multi_map_server diff --git a/jsk_rosbag_tools/.gitignore b/jsk_rosbag_tools/.gitignore new file mode 100644 index 000000000..486fd4ec6 --- /dev/null +++ b/jsk_rosbag_tools/.gitignore @@ -0,0 +1,2 @@ +requirements.txt +!.gitignore diff --git a/jsk_rosbag_tools/CMakeLists.txt b/jsk_rosbag_tools/CMakeLists.txt new file mode 100644 index 000000000..5fd30db9c --- /dev/null +++ b/jsk_rosbag_tools/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 2.8.3) +project(jsk_rosbag_tools) + +find_package( + catkin REQUIRED + catkin_virtualenv +) + +catkin_python_setup() + +catkin_package( + CATKIN_DEPENDS +) + +if (${catkin_virtualenv_VERSION} VERSION_LESS "0.6.1") + message(WARNING "Please install catkin_virtualenv>=0.6.1.") + message(WARNING "Current catkin_virtualen version is ${catkin_virtualenv_VERSION}") +else() + catkin_generate_virtualenv( + INPUT_REQUIREMENTS requirements.in + PYTHON_INTERPRETER python3 + USE_SYSTEM_PACKAGES TRUE + ISOLATE_REQUIREMENTS FALSE + CHECK_VENV FALSE + ) + file(GLOB SCRIPTS_FILES scripts/*) + catkin_install_python( + PROGRAMS ${SCRIPTS_FILES} + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} + ) +endif() + +catkin_download(download_audio_data + https://drive.google.com/uc?export=download&id=1rFZYoFjLqIWjEe0DaNiL3k9893m31nu7 + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/samples/data + FILENAME 2022-05-07-hello-test.bag + MD5 3650e27dad2c7dc0e447033259290db6 +) +catkin_download(download_video_data + https://drive.google.com/uc?export=download&id=1v4YNOHnHYxLOty1lYR2R6lfNF0itCwK7 + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/samples/data + FILENAME 20220530173950_go_to_kitchen_rosbag.bag + MD5 d6a146f06e1a62430f1933704cc7a740 +) +add_custom_target(download ALL DEPENDS download_audio_data download_video_data) + +install(FILES requirements.in requirements.txt + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +) + + +if(CATKIN_ENABLE_TESTING) + find_package(catkin REQUIRED COMPONENTS catkin_virtualenv roslint rostest) + + catkin_generate_virtualenv( + INPUT_REQUIREMENTS requirements.in + PYTHON_INTERPRETER python3 + ) + set(python_test_scripts + tests/test_jsk_rosbag_tools.py + ) + + roslint_python() + roslint_python(${python_test_scripts}) + roslint_add_test() + + catkin_install_python(PROGRAMS ${python_test_scripts} + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) + + add_rostest(tests/test_jsk_rosbag_tools.test + DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv + ) +endif() diff --git a/jsk_rosbag_tools/README.md b/jsk_rosbag_tools/README.md new file mode 100644 index 000000000..b2e82d912 --- /dev/null +++ b/jsk_rosbag_tools/README.md @@ -0,0 +1,155 @@ +# jsk_rosbag_tools + +Tools such as creating video from rosbag and compressing rosbag images. + +## bag_to_video.py + +Create video from rosbag. + +### Usage + +``` +usage: bag_to_video.py [-h] [--out OUT] [--fps FPS] [--samplerate SAMPLERATE] [--channels CHANNELS] [--audio-topic AUDIO_TOPIC] [--image-topics IMAGE_TOPICS [IMAGE_TOPICS ...]] input_bagfile + +rosbag to video + +positional arguments: + input_bagfile + +optional arguments: + -h, --help show this help message and exit + --out OUT, -o OUT output directory path + --fps FPS + --samplerate SAMPLERATE, -r SAMPLERATE + sampling rate + --channels CHANNELS number of input channels + --audio-topic AUDIO_TOPIC + --image-topics IMAGE_TOPICS [IMAGE_TOPICS ...] + Topic name to extract. +``` + +### Example + +``` +rosrun jsk_rosbag_tools bag_to_video.py $(rospack find jsk_rosbag_tools)/samples/data/20220530173950_go_to_kitchen_rosbag.bag \ + --samplerate 16000 --channels 1 --audio-topic /audio \ + --image-topics /head_camera/rgb/throttled/image_rect_color/compressed \ + -o /tmp/output_bag +``` + +## video_to_bag.py + +Convert video file to bagfile. + +### Usage + +``` +usage: video_to_bag.py [-h] [--out output_file] [--topic-name TOPIC_NAME] [--compress] [--no-progress-bar] inputvideo + +Convert video to bag. + +positional arguments: + inputvideo + +optional arguments: + -h, --help show this help message and exit + --out output_file, -o output_file + name of the output bag file + --topic-name TOPIC_NAME + Converted topic name. + --compress Compress Image flag. + --no-progress-bar Don't show progress bar. +``` + +### Example + +``` +rosrun jsk_rosbag_tools video_to_bag.py /tmp/output_bag/head_camera--slash--rgb--slash--throttled--slash--image_rect_color--slash--compressed-with-audio.mp4 \ + -o /tmp/output_bag/video.bag --compress +``` + +## compress_imgs.py + +Convert `Image` messages to `CompressedImage` or `CompressedDepthImage`. + +### Usage + +``` +usage: compress_imgs.py [-h] [--out OUT] [--compressed-topics [COMPRESSED_TOPICS [COMPRESSED_TOPICS ...]]] [--replace] [--no-progress-bar] input_bagfile + +Convert Image messages to CompressedImage or CompressedDepthImage + +positional arguments: + input_bagfile input bagfile path + +optional arguments: + -h, --help show this help message and exit + --out OUT, -o OUT output bagfile path + --compressed-topics [COMPRESSED_TOPICS [COMPRESSED_TOPICS ...]] + this image topics are compressed + --replace + --no-progress-bar Don't show progress bar. +``` + +### Example + +``` +rosrun jsk_rosbag_tools compress_imgs.py $(rospack find jsk_rosbag_tools)/samples/data/20220530173950_go_to_kitchen_rosbag.bag \ + -o /tmp/20220530173950_go_to_kitchen_rosbag-compressed.bag +``` + +## tf_static_to_tf.py + +Convert tf_static to tf and save it as a rosbag. + +``` +usage: tf_static_to_tf.py [-h] [--out OUT] [--no-progress-bar] input_bagfile + +Convert tf_static to tf and save it as a rosbag + +positional arguments: + input_bagfile input bagfile path + +optional arguments: + -h, --help show this help message and exit + --out OUT, -o OUT output bagfile path + --no-progress-bar Don't show progress bar. +``` + +### Example + +``` +rosrun jsk_rosbag_tools tf_static_to_tf.py $(rospack find jsk_rosbag_tools)/samples/data/20220530173950_go_to_kitchen_rosbag.bag +``` + +## merge.py + +Merges two bagfiles. + +### Usage + +``` +usage: merge.py [-h] [--out output_file] [--topics TOPICS] [-i] main_bagfile bagfile + +Merges two bagfiles. + +positional arguments: + main_bagfile path to a bagfile, which will be the main bagfile + bagfile path to a bagfile which should be merged to the main bagfile + +optional arguments: + -h, --help show this help message and exit + --out output_file, -o output_file + name of the output file + --topics TOPICS, -t TOPICS + topics which should be merged to the main bag + -i reindex bagfile +``` + +### Example + +``` +rosrun jsk_rosbag_tools merge.py \ + $(rospack find jsk_rosbag_tools)/samples/data/20220530173950_go_to_kitchen_rosbag.bag \ + $(rospack find jsk_rosbag_tools)/samples/data/2022-05-07-hello-test.bag +``` diff --git a/jsk_rosbag_tools/package.xml b/jsk_rosbag_tools/package.xml new file mode 100644 index 000000000..694489705 --- /dev/null +++ b/jsk_rosbag_tools/package.xml @@ -0,0 +1,35 @@ + + + jsk_rosbag_tools + 0.0.1 + The rosbag tools + BSD + + Iori Yanokura + Iori Yanokura + + catkin + python3-catkin-pkg-modules + + catkin_virtualenv + cv_bridge + cv_bridge_python3 + sensor_msgs + python3-empy + python3-gnupg + python3-numpy + python3-pycryptodome + python3-rospkg-modules + python3-setuptools + python3-termcolor + python3-tqdm + python3-yaml + libsndfile1-dev + + roslint + rostest + + + requirements.txt + + diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/__init__.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/__init__.py new file mode 100644 index 000000000..cace884b2 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/__init__.py @@ -0,0 +1,5 @@ +# flake8: noqa +from jsk_rosbag_tools.cv_bridge_compat import cv_bridge +from jsk_rosbag_tools.cv_bridge_compat import CvBridge + +from jsk_rosbag_tools.cv_compat import cv2 diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/compress.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/compress.py new file mode 100644 index 000000000..eeed738f8 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/compress.py @@ -0,0 +1,37 @@ +import rosbag +from tqdm import tqdm + +from jsk_rosbag_tools.cv import compress_depth_msg +from jsk_rosbag_tools.cv import compress_img_msg +from jsk_rosbag_tools.info import get_topic_dict + + +def compress_bag_imgs(input_bagfilepath, output_bagfilepath, + compressed_topics=None, + show_progress_bar=True): + compressed_topics = compressed_topics or [] + input_bag = rosbag.Bag(input_bagfilepath) + + topic_dict = get_topic_dict(input_bagfilepath) + with rosbag.Bag(output_bagfilepath, 'w') as outbag: + if show_progress_bar: + progress = tqdm(total=input_bag.get_message_count()) + for topic, msg, t in input_bag: + if show_progress_bar: + progress.update(1) + # update the progress with a post fix + progress.set_postfix(time=t) + if topic_dict[topic]['type'] == 'sensor_msgs/Image': + if msg.encoding in ['bgra8', 'bgr8', + 'rgba8', 'rgb8']: + msg = compress_img_msg(msg) + topic += '/compressed' + elif msg.encoding in ['16UC1', '32FC1']: + msg = compress_depth_msg(msg) + if topic.lstrip('/') in compressed_topics: + topic += '/compressed' + else: + topic += '/compressedDepth' + else: + pass + outbag.write(topic, msg, t) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py new file mode 100644 index 000000000..2eb3ab049 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py @@ -0,0 +1,179 @@ +import struct + +import cv2 +import numpy as np +import sensor_msgs.msg + +from jsk_rosbag_tools.cv_bridge_compat import cv_bridge + + +_bridge = cv_bridge.CvBridge() + + +def img_to_msg(img, encoding="bgr8", + compress=True): + """Convert numpy image to ROS message. + + """ + msg = _bridge.cv2_to_imgmsg(img, encoding=encoding) + if compress is True: + msg = compress_img_msg(msg) + return msg + + +def msg_to_img(msg): + if msg.encoding in ['bgra8', 'bgr8', + 'rgba8', 'rgb8']: + return msg_to_bgr(msg) + else: + return msg_to_np_depth(msg) + + +def compressed_format(msg): + fmt, compr_type = msg.format.split(';') + # remove white space + fmt = fmt.strip() + compr_type = compr_type.strip() + return fmt, compr_type + + +def decompresse_imgmsg(msg): + fmt, compr_type = compressed_format(msg) + if compr_type == 'compressedDepth': + return msg_to_np_depth(msg, compressed=True) + else: + return msg_to_bgr(msg, compressed=True) + + +def msg_to_bgr(msg, compressed=False): + if compressed: + np_arr = np.fromstring( + msg.data, np.uint8) + img = cv2.imdecode( + np_arr, cv2.IMREAD_COLOR) + if msg.format == 'rgb8' or msg.format == 'rgba8': + bgr_img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + else: + bgr_img = img + else: + img = _bridge.imgmsg_to_cv2( + msg, + desired_encoding=msg.encoding) + if msg.encoding == 'rgb8' or msg.encoding == 'rgba8': + bgr_img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + else: + bgr_img = img + return bgr_img + + +def msg_to_np_depth(msg, compressed=False, rescale=True): + if compressed: + # 'msg' as type CompressedImage + depth_fmt, compr_type = msg.format.split(';') + # remove white space + depth_fmt = depth_fmt.strip() + compr_type = compr_type.strip() + if compr_type != "compressedDepth": + raise Exception("Compression type is not 'compressedDepth'." + "You probably subscribed to the wrong topic. " + "get compresstion_type: {}".format(compr_type)) + + # remove header from raw data + depth_header_size = 12 + raw_data = msg.data[depth_header_size:] + + depth_img_raw = cv2.imdecode(np.fromstring(raw_data, np.uint8), + cv2.IMREAD_UNCHANGED) + if depth_img_raw is None: + # probably wrong header size + raise Exception("Could not decode compressed depth image." + "You may need to change 'depth_header_size'!") + + if depth_fmt == "16UC1": + if rescale: + depth_img = depth_img_raw / 1000.0 + else: + depth_img = depth_img_raw + elif depth_fmt == "32FC1": + raw_header = msg.data[:depth_header_size] + # header: int, float, float + [compfmt, depthQuantA, depthQuantB] = struct.unpack('iff', + raw_header) + if rescale: + depth_img_scaled = depthQuantA / ( + depth_img_raw.astype(np.float32) - depthQuantB) + else: + depth_img_scaled = depth_img_raw + # filter max values + depth_img_scaled[depth_img_raw == 0] = 0 + + # depth_img_scaled provides distance in meters as f32 + # for storing it as png, we need to convert + # it to 16UC1 again (depth in mm) + depth_img = depth_img_scaled + else: + raise Exception("Decoding of '" + depth_fmt + + "' is not implemented!") + else: + depth_img = _bridge.imgmsg_to_cv2( + msg, + desired_encoding='passthrough') + if msg.encoding == '16UC1': + if rescale: + depth_img = np.asarray(depth_img, dtype=np.float32) + depth_img /= 1000.0 # convert metric: mm -> m + elif msg.encoding == '32FC1': + pass + else: + print('Unsupported depth encoding: %s' + % msg.encoding) + return depth_img + + +def compress_img_msg(msg): + compressed_msg = sensor_msgs.msg.CompressedImage( + header=msg.header) + compressed_msg.format = msg.encoding + '; jpeg compressed bgr8' + + img = msg_to_bgr(msg, compressed=False) + compressed_msg.data = np.array(cv2.imencode( + '.jpg', img)[1]).tostring() + return compressed_msg + + +def compress_depth_msg(msg, depth_quantization=100, depth_max=None): + compressed_msg = sensor_msgs.msg.CompressedImage( + header=msg.header) + compressed_msg.format = '{}; compressedDepth'.format( + msg.encoding) + if msg.encoding == '32FC1': + depth = msg_to_np_depth( + msg, compressed=False, rescale=True) + if depth_max is None: + depth_max = depth.max() + 1.0 + depth_quant_a = depth_quantization * (depth_quantization + 1.0) + depth_quant_b = 1.0 - depth_quant_a / depth_max + inv_depth_img = np.zeros_like(depth, dtype=np.uint16) + target_pixel = np.logical_and(depth_max > depth, depth > 0) + inv_depth_img[target_pixel] = depth_quant_a / \ + depth[target_pixel] + depth_quant_b + + compressed_msg.data = struct.pack( + 'iff', 0, depth_quant_a, depth_quant_b) + compressed_msg.data += np.array( + cv2.imencode('.png', inv_depth_img)[1]).tostring() + elif msg.encoding == '16UC1': + depth = msg_to_np_depth( + msg, compressed=False, rescale=False).copy() + if depth_max is None: + depth_max = depth.max() + depth_max_ushort = depth_max * 1000.0 + depth[depth > depth_max_ushort] = 0 + + compressed_msg.data = " " * 12 + compressed_msg.data += np.array( + cv2.imencode('.png', depth)[1]).tostring() + else: + raise NotImplementedError + + return compressed_msg diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_bridge_compat.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_bridge_compat.py new file mode 100644 index 000000000..78c2b8241 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_bridge_compat.py @@ -0,0 +1,35 @@ +import importlib +import os +import sys + + +# cv_bridge_python3 import +if os.environ['ROS_PYTHON_VERSION'] == '3': + cv_bridge = importlib.import_module("cv_bridge") + CvBridge = getattr(cv_bridge, 'CvBridge') +else: + ws_python3_paths = [p for p in sys.path if 'devel/lib/python3' in p] + if len(ws_python3_paths) == 0: + # search cv_bridge in workspace and append + ws_python2_paths = [ + p for p in sys.path if 'devel/lib/python2.7' in p] + for ws_python2_path in ws_python2_paths: + ws_python3_path = ws_python2_path.replace('python2.7', 'python3') + if os.path.exists(os.path.join(ws_python3_path, 'cv_bridge')): + ws_python3_paths.append(ws_python3_path) + if len(ws_python3_paths) == 0: + opt_python3_path = '/opt/ros/{}/lib/python3/dist-packages'.format( + os.getenv('ROS_DISTRO')) + sys.path = [opt_python3_path] + sys.path + cv_bridge = importlib.import_module("cv_bridge") + CvBridge = getattr(cv_bridge, 'CvBridge') + sys.path.remove(opt_python3_path) + else: + sys.path = [ws_python3_paths[0]] + sys.path + cv_bridge = importlib.import_module("cv_bridge") + CvBridge = importlib.import_module("cv_bridge.CvBridge") + CvBridge = getattr(cv_bridge, 'CvBridge') + sys.path.remove(ws_python3_paths[0]) + else: + cv_bridge = importlib.import_module("cv_bridge") + CvBridge = getattr(cv_bridge, 'CvBridge') diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_compat.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_compat.py new file mode 100644 index 000000000..68b4457f0 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_compat.py @@ -0,0 +1,14 @@ +import importlib +import os +import sys + + +# OpenCV import for python3 +if os.environ['ROS_PYTHON_VERSION'] == '3': + cv2 = importlib.import_module("cv2") +else: + sys.path.remove('/opt/ros/{}/lib/python2.7/dist-packages' + .format(os.getenv('ROS_DISTRO'))) + cv2 = importlib.import_module("cv2") + sys.path.append('/opt/ros/{}/lib/python2.7/dist-packages' + .format(os.getenv('ROS_DISTRO'))) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py new file mode 100644 index 000000000..cd50c66da --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py @@ -0,0 +1,138 @@ +import os + +import numpy as np +from pydub import AudioSegment +from pydub import effects +import rosbag +import soundfile as sf + +from jsk_rosbag_tools.cv import compressed_format +from jsk_rosbag_tools.cv import decompresse_imgmsg +from jsk_rosbag_tools.cv import msg_to_img +from jsk_rosbag_tools.info import get_topic_dict + + +def get_image_topic_names(bag_filepath, + rgb_only=False): + topic_dict = get_topic_dict(bag_filepath) + topic_names = [] + for topic_name, info in topic_dict.items(): + if info['type'] == 'sensor_msgs/Image' or \ + info['type'] == 'sensor_msgs/CompressedImage': + if rgb_only: + msg = extract_oneshot_topic(bag_filepath, topic_name) + if topic_dict[topic_name]['type'] == 'sensor_msgs/Image': + encoding = msg.encoding + else: + encoding, _ = compressed_format(msg) + if encoding in ['bgra8', 'bgr8', + 'rgba8', 'rgb8']: + topic_names.append(topic_name) + else: + topic_names.append(topic_name) + return topic_names + + +def extract_oneshot_topic(bag_filepath, topic_name): + topic_dict = get_topic_dict(bag_filepath) + + if topic_name not in topic_dict: + raise ValueError('Topic not exists {}'. + format(topic_name)) + + msg = None + with rosbag.Bag(bag_filepath, 'r') as input_rosbag: + for topic, msg, stamp in input_rosbag.read_messages( + topics=[topic_name, ]): + break + return msg + + +def extract_image_topic(bag_filepath, topic_name): + topic_dict = get_topic_dict(bag_filepath) + if topic_name not in topic_dict: + raise ValueError("topic ({}) is not included in bagfile ({})." + .format(topic_name, bag_filepath)) + + with rosbag.Bag(bag_filepath, 'r') as input_rosbag: + for topic, msg, _ in input_rosbag.read_messages( + topics=[topic_name]): + if topic_dict[topic]['type'] == 'sensor_msgs/Image': + bgr_img = msg_to_img(msg) + encoding = msg.encoding + elif topic_dict[topic]['type'] == \ + 'sensor_msgs/CompressedImage': + bgr_img = decompresse_imgmsg(msg) + encoding, _ = compressed_format(msg) + else: + raise RuntimeError + + # padding image + if bgr_img.shape[0] % 2 != 0: + if bgr_img.ndim == 2: + pad_img = np.zeros( + (1, bgr_img.shape[1]), dtype=bgr_img.dtype) + elif bgr_img.ndim == 3: + pad_img = np.zeros( + (1, bgr_img.shape[1], bgr_img.shape[2]), + dtype=bgr_img.dtype) + else: + raise ValueError + bgr_img = np.concatenate([bgr_img, pad_img], axis=0) + + if bgr_img.shape[1] % 2 != 0: + if bgr_img.ndim == 2: + pad_img = np.zeros( + (bgr_img.shape[0], 1), dtype=bgr_img.dtype) + elif bgr_img.ndim == 3: + pad_img = np.zeros( + (bgr_img.shape[0], 1, bgr_img.shape[2]), + dtype=bgr_img.dtype) + else: + raise ValueError + bgr_img = np.concatenate([bgr_img, pad_img], axis=1) + + yield msg.header.stamp.to_sec(), topic, bgr_img, encoding + + +def extract_audio(bag_filepath, + wav_outpath, + topic_name='/audio', + audio_info_topic_name=None, + samplerate=44100, + channels=1, + normalize=True, + overwrite=True): + topic_dict = get_topic_dict(bag_filepath) + if topic_name not in topic_dict: + return + + audio_info_topic_name = audio_info_topic_name or topic_name + '_info' + # if audio_info_topic exists, extract info from it. + if audio_info_topic_name in topic_dict: + audio_info = extract_oneshot_topic(bag_filepath, audio_info_topic_name) + if audio_info is not None: + samplerate = audio_info.sample_rate + channels = audio_info.channels + + bag = rosbag.Bag(bag_filepath) + with sf.SoundFile(wav_outpath, mode='w' if overwrite else 'x', + samplerate=samplerate, + channels=channels, + format='wav') as f: + for _, msg, _ in bag.read_messages(topics=[topic_name]): + if msg._type == 'audio_common_msgs/AudioData': + audio_buffer = np.frombuffer(msg.data, dtype='int16') + audio_buffer = audio_buffer.reshape(-1, channels) + f.write(audio_buffer) + + valid = os.stat(wav_outpath).st_size != 0 + if valid is False: + return False + + if normalize: + sound = AudioSegment.from_file(wav_outpath) + normalized_sound = effects.normalize(sound, 1.0) + normalized_sound.export(wav_outpath, format='wav') + + return True diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/info.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/info.py new file mode 100644 index 000000000..015489f5c --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/info.py @@ -0,0 +1,22 @@ +import os + +import rosbag +import yaml + + +def get_info(bag_filepath): + if not os.path.exists(bag_filepath): + raise OSError('bag file {} not exists'.format(bag_filepath)) + info_dict = yaml.load( + rosbag.Bag(bag_filepath)._get_yaml_info(), + Loader=yaml.SafeLoader) + return info_dict + + +def get_topic_dict(bag_filepath): + info_dict = get_info(bag_filepath) + topics = info_dict['topics'] + topic_dict = {} + for topic in topics: + topic_dict[topic['topic']] = topic + return topic_dict diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/merge.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/merge.py new file mode 100644 index 000000000..40dc7e24c --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/merge.py @@ -0,0 +1,83 @@ +import os + +import rosbag + + +def get_next(bag_iter, reindex=False, + main_start_time=None, start_time=None, + topics=None): + try: + result = next(bag_iter) + if topics is not None: + while not result[0] in topics: + result = next(bag_iter) + if reindex: + return (result[0], result[1], + result[2] - start_time + main_start_time) + return result + except StopIteration: + return None + + +def merge_bag(main_bagfile, bagfile, outfile=None, topics=None, + reindex=True): + # get min and max time in bagfile + main_limits = get_limits(main_bagfile) + limits = get_limits(bagfile) + # check output file + if outfile is None: + pattern = main_bagfile + "_merged_%i.bag" + outfile = main_bagfile + "_merged.bag" + index = 0 + while (os.path.exists(outfile)): + outfile = pattern % index + index += 1 + # output some information + print("merge bag %s in %s" % (bagfile, main_bagfile)) + print("topics filter: ", topics) + print("writing to %s." % outfile) + # merge bagfile + outbag = rosbag.Bag(outfile, 'w') + main_bag = rosbag.Bag(main_bagfile).__iter__() + bag = rosbag.Bag(bagfile).__iter__() + main_next = get_next(main_bag) + next = get_next(bag, reindex, main_limits[0], limits[0], topics) + try: + while main_next is not None or next is not None: + if main_next is None: + outbag.write(next[0], next[1], next[2]) + next = get_next( + bag, + reindex, + main_limits[0], + limits[0], + topics) + elif next is None: + outbag.write(main_next[0], main_next[1], main_next[2]) + main_next = get_next(main_bag) + elif next[2] < main_next[2]: + outbag.write(next[0], next[1], next[2]) + next = get_next( + bag, + reindex, + main_limits[0], + limits[0], + topics) + else: + outbag.write(main_next[0], main_next[1], main_next[2]) + main_next = get_next(main_bag) + finally: + outbag.close() + + +def get_limits(bagfile): + print("Determine start and end index of %s..." % bagfile) + end_time = None + start_time = None + + for topic, msg, t in rosbag.Bag(bagfile).read_messages(): + if start_time is None or t < start_time: + start_time = t + if end_time is None or t > end_time: + end_time = t + return (start_time, end_time) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py new file mode 100644 index 000000000..0415ef494 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py @@ -0,0 +1,179 @@ +from __future__ import division + +import datetime +import math +import os.path as osp +import shutil +import subprocess +import tempfile + +import audio_common_msgs.msg +import numpy as np +from pydub.utils import mediainfo +import rosbag +import rospy +import skvideo.io +import soundfile as sf +from tqdm import tqdm + +from jsk_rosbag_tools.cv import img_to_msg +from jsk_rosbag_tools.cv_compat import cv2 +from jsk_rosbag_tools.merge import merge_bag + + +def nsplit(xlst, n): + total_n = len(xlst) + d = int((total_n + n - 1) / n) + i = 0 + ret = [] + while i < total_n: + ret.append(xlst[i:i + d]) + i += d + return ret + + +def get_video_duration(video_path): + video_path = str(video_path) + if not osp.exists(video_path): + raise OSError("{} not exists".format(video_path)) + metadata = skvideo.io.ffprobe(video_path) + return float(metadata['video']['@duration']) + + +def get_video_avg_frame_rate(video_path): + video_path = str(video_path) + if not osp.exists(video_path): + raise OSError("{} not exists".format(video_path)) + metadata = skvideo.io.ffprobe(video_path) + a, b = metadata['video']['@avg_frame_rate'].split('/') + a = int(a) + b = int(b) + return a / b + + +def get_video_creation_time(video_path): + metadata = skvideo.io.ffprobe(video_path) + tag_dict = {} + for tag in metadata['video']['tag']: + tag_dict[tag['@key']] = tag['@value'] + if 'creation_time' not in tag_dict: + return None + creation_time = tag_dict['creation_time'] + created_at = datetime.datetime.strptime( + creation_time, '%Y-%m-%dT%H:%M:%S.%fZ') + return created_at + + +def get_video_n_frame(video_path): + video_path = str(video_path) + if not osp.exists(video_path): + raise OSError("{} not exists".format(video_path)) + metadata = skvideo.io.ffprobe(video_path) + if '@nb_frames' not in metadata['video']: + fps = get_video_avg_frame_rate(video_path) + return int(fps * get_video_duration(video_path)) + return int(metadata['video']['@nb_frames']) + + +def load_frame(video_path, start=0.0, duration=-1, + target_size=None, sampling_frequency=None): + video_path = str(video_path) + vid = cv2.VideoCapture(video_path) + fps = vid.get(cv2.CAP_PROP_FPS) + vid.set(cv2.CAP_PROP_POS_MSEC, start) + vid_avail = True + if sampling_frequency is not None: + frame_interval = int(math.ceil(fps * sampling_frequency)) + else: + frame_interval = 1 + cur_frame = 0 + while True: + stamp = float(cur_frame) / fps + vid_avail, frame = vid.read() + if not vid_avail: + break + if duration != -1 and stamp > start + duration: + break + if target_size is not None: + frame = cv2.resize(frame, target_size) + yield frame, stamp + cur_frame += frame_interval + vid.set(cv2.CAP_PROP_POS_FRAMES, cur_frame) + vid.release() + + +def video_to_bag(video_filepath, bag_output_filepath, + topic_name, compress=False, audio_topic_name='/audio', + no_audio=False, + base_unixtime=None, + show_progress_bar=True): + if base_unixtime is None: + base_unixtime = get_video_creation_time(video_filepath) + if base_unixtime is None: + base_unixtime = datetime.datetime.now() + base_unixtime = base_unixtime.timestamp() + + topic_name = topic_name.lstrip('/compressed') + if compress is True: + topic_name = osp.join(topic_name, 'compressed') + + with tempfile.TemporaryDirectory() as tmpdirname: + video_out = osp.join(tmpdirname, 'video.tmp.bag') + n_frame = get_video_n_frame(video_filepath) + if show_progress_bar: + progress = tqdm(total=n_frame) + with rosbag.Bag(video_out, 'w') as outbag: + for img, timestamp in load_frame(video_filepath): + if show_progress_bar: + progress.update(1) + msg = img_to_msg(img, compress=compress) + sec = int(base_unixtime + timestamp) + nsec = ((base_unixtime + timestamp) * (10 ** 9)) % (10 ** 9) + ros_timestamp = rospy.rostime.Time(sec, nsec) + msg.header.stamp = ros_timestamp + outbag.write(topic_name, msg, ros_timestamp) + + extract_audio = True + if no_audio is False: + wav_filepath = osp.join(tmpdirname, 'tmp.wav') + cmd = "ffmpeg -i '{}' '{}'".format( + video_filepath, wav_filepath) + subprocess.run(cmd, shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + try: + data, sample_rate = sf.read(wav_filepath, dtype='int16') + media_info = mediainfo(wav_filepath) + except RuntimeError: + extract_audio = False + + if extract_audio: + rate = 100 + n = int(np.ceil(data.shape[0] / (sample_rate // rate))) + channels = data.shape[1] + + audio_out = osp.join(tmpdirname, 'audio.tmp.bag') + with rosbag.Bag(audio_out, 'w') as outbag: + audio_info = audio_common_msgs.msg.AudioInfo( + channels=channels, + sample_rate=sample_rate, + sample_format=media_info['codec_name'].upper(), + bitrate=int(media_info['bit_rate']), + coding_format='wave') + outbag.write(audio_topic_name + '_info', + audio_info, ros_timestamp) + for i, audio_data in enumerate(nsplit(data, n)): + msg = audio_common_msgs.msg.AudioData() + msg.data = audio_data.reshape(-1).tobytes() + timestamp = i * 0.01 + sec = int(base_unixtime + timestamp) + nsec = ((base_unixtime + timestamp) + * (10 ** 9)) % (10 ** 9) + ros_timestamp = rospy.rostime.Time(sec, nsec) + outbag.write(audio_topic_name, msg, ros_timestamp) + merge_bag(video_out, audio_out, bag_output_filepath) + else: + shutil.move(video_out, bag_output_filepath) + else: + shutil.move(video_out, bag_output_filepath) diff --git a/jsk_rosbag_tools/requirements.in b/jsk_rosbag_tools/requirements.in new file mode 100644 index 000000000..fecff2535 --- /dev/null +++ b/jsk_rosbag_tools/requirements.in @@ -0,0 +1,4 @@ +moviepy +pydub +scikit-video +soundfile diff --git a/jsk_rosbag_tools/samples/data/.gitignore b/jsk_rosbag_tools/samples/data/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/jsk_rosbag_tools/samples/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/jsk_rosbag_tools/scripts/bag_to_video.py b/jsk_rosbag_tools/scripts/bag_to_video.py new file mode 100644 index 000000000..745225ebe --- /dev/null +++ b/jsk_rosbag_tools/scripts/bag_to_video.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +import argparse +import datetime +import os +import sys + +import moviepy.editor as mp +from moviepy.video.io.ffmpeg_writer import FFMPEG_VideoWriter + +from jsk_rosbag_tools import cv2 +from jsk_rosbag_tools.extract import extract_audio +from jsk_rosbag_tools.extract import extract_image_topic +from jsk_rosbag_tools.extract import get_image_topic_names + + +def main(): + parser = argparse.ArgumentParser(description='rosbag to video') + parser.add_argument('--out', '-o', default='', + help='output directory path') + parser.add_argument('--fps', default=30, + type=int) + parser.add_argument('--samplerate', '-r', type=int, help='sampling rate', + default='16000') + parser.add_argument('--channels', + type=int, default=1, help='number of input channels') + parser.add_argument('--audio-topic', type=str, default='/audio') + parser.add_argument('--image-topics', type=str, + nargs='+', help='Topic name to extract.') + parser.add_argument('input_bagfile') + args = parser.parse_args() + + input_bagfile = args.input_bagfile + if not os.path.exists(input_bagfile): + print('Input bagfile {} not exists.'.format(input_bagfile)) + sys.exit(1) + + if len(args.out) == 0: + basename = os.path.basename(input_bagfile) + dirname = os.path.dirname(input_bagfile) + filename, _ = os.path.splitext(basename) + out = os.path.join(dirname, filename) + else: + out = args.out + os.makedirs(out, mode=0o777, exist_ok=True) + + fps = args.fps + dt = 1.0 / fps + + wav_outpath = os.path.join(out, 'audio.wav') + audio_exists = extract_audio(input_bagfile, wav_outpath, + samplerate=args.samplerate, + channels=args.channels, + topic_name=args.audio_topic) + + candidates_topic_names = get_image_topic_names( + input_bagfile, rgb_only=True) + if args.image_topics is None: + topic_names = candidates_topic_names + else: + topic_names = [tn for tn in args.image_topics + if tn in candidates_topic_names] + + for topic_name in topic_names: + images = extract_image_topic(input_bagfile, topic_name) + + # remove 0 time stamp + stamp = 0.0 + while stamp == 0.0: + stamp, _, img, _ = next(images) + start_stamp = stamp + + if topic_name[0] == '/': + replaced_topic_name = topic_name[1:] + else: + replaced_topic_name = topic_name + replaced_topic_name = replaced_topic_name.replace('/', '--slash--') + output_videopath = os.path.join( + out, "{}.mp4".format(replaced_topic_name)) + + creation_time = datetime.datetime.utcfromtimestamp(start_stamp) + time_format = '%y-%m-%d %h:%M:%S' + writer = FFMPEG_VideoWriter( + output_videopath, + (img.shape[1], img.shape[0]), + fps, logfile=None, + ffmpeg_params=[ + '-metadata', + 'creation_time={}'.format( + creation_time.strftime(time_format)), + ]) + + current_time = 0.0 + cur_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + for stamp, _, bgr_img, _ in images: + rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) + aligned_stamp = stamp - start_stamp + while current_time < aligned_stamp: + current_time += dt + writer.write_frame(cur_img) + cur_img = rgb_img + writer.write_frame(cur_img) + current_time += dt + writer.close() + + if audio_exists: + output_video_with_audio_path = os.path.join( + out, "{}-with-audio.mp4".format(replaced_topic_name)) + clip_output = mp.VideoFileClip(output_videopath).subclip().\ + set_audio(mp.AudioFileClip(wav_outpath)) + clip_output.write_videofile( + output_video_with_audio_path) + + +if __name__ == '__main__': + main() diff --git a/jsk_rosbag_tools/scripts/compress_imgs.py b/jsk_rosbag_tools/scripts/compress_imgs.py new file mode 100644 index 000000000..3c05ef89d --- /dev/null +++ b/jsk_rosbag_tools/scripts/compress_imgs.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +import argparse +import os +import shutil + +import termcolor + +from jsk_rosbag_tools.compress import compress_bag_imgs + + +def main(): + parser = argparse.ArgumentParser( + description='Convert Image messages ' + 'to CompressedImage or CompressedDepthImage') + parser.add_argument('input_bagfile', help='input bagfile path') + parser.add_argument('--out', '-o', default='', + help='output bagfile path') + parser.add_argument('--compressed-topics', nargs='*', + default=[], + help='this image topics are compressed') + parser.add_argument('--replace', action='store_true') + parser.add_argument('--no-progress-bar', action='store_true', + help="Don't show progress bar.") + args = parser.parse_args() + compressed_topics = [t.lstrip('/') for t in args.compressed_topics] + + input_bagfile = args.input_bagfile + if len(args.out) == 0: + basename = os.path.basename(input_bagfile) + dirname = os.path.dirname(input_bagfile) + filename, ext = os.path.splitext(basename) + out = os.path.join(dirname, '{}-image-compressed{}' + .format(filename, ext)) + else: + out = args.out + compress_bag_imgs(input_bagfile, out, + compressed_topics=compressed_topics, + show_progress_bar=not args.no_progress_bar) + if args.replace: + termcolor.cprint('=> Replaced to {}'.format(out), 'green') + shutil.move(out, input_bagfile) + else: + termcolor.cprint('=> Saved to {}'.format(out), 'green') + + +if __name__ == '__main__': + main() diff --git a/jsk_rosbag_tools/scripts/merge.py b/jsk_rosbag_tools/scripts/merge.py new file mode 100644 index 000000000..955db22b8 --- /dev/null +++ b/jsk_rosbag_tools/scripts/merge.py @@ -0,0 +1,37 @@ +import argparse + +from jsk_rosbag_tools.merge import merge_bag + + +def main(): + parser = argparse.ArgumentParser(description='Merges two bagfiles.') + parser.add_argument('--out', '-o', + type=str, help='name of the output file', + default=None, metavar="output_file") + parser.add_argument( + '--topics', '-t', + type=str, + help='topics which should be merged to the main bag', + default=None) + parser.add_argument('-i', help='reindex bagfile', + default=False, action="store_true") + parser.add_argument( + 'main_bagfile', + type=str, + help='path to a bagfile, which will be the main bagfile') + parser.add_argument( + 'bagfile', + type=str, + help='path to a bagfile which should be merged to the main bagfile') + args = parser.parse_args() + if args.topics is not None: + args.topics = args.topics.split(',') + merge_bag(args.main_bagfile, + args.bagfile, + outfile=args.out, + topics=args.topics, + reindex=args.i) + + +if __name__ == "__main__": + main() diff --git a/jsk_rosbag_tools/scripts/tf_static_to_tf.py b/jsk_rosbag_tools/scripts/tf_static_to_tf.py new file mode 100644 index 000000000..512a4bb77 --- /dev/null +++ b/jsk_rosbag_tools/scripts/tf_static_to_tf.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import argparse +import os + +import rosbag +import rospy +import termcolor +from tqdm import tqdm + + +def main(): + parser = argparse.ArgumentParser( + description='Convert tf_static to tf and save it as a rosbag') + parser.add_argument('input_bagfile', help='input bagfile path') + parser.add_argument('--out', '-o', default='', + help='output bagfile path') + parser.add_argument('--no-progress-bar', action='store_true', + help="Don't show progress bar.") + args = parser.parse_args() + + input_bagfile = args.input_bagfile + if len(args.out) == 0: + basename = os.path.basename(input_bagfile) + dirname = os.path.dirname(input_bagfile) + filename, ext = os.path.splitext(basename) + out = os.path.join(dirname, '{}--tf_static--converted{}' + .format(filename, ext)) + else: + out = args.out + + input_bag = rosbag.Bag(input_bagfile) + tf_static_messages = list(input_bag.read_messages(('/tf_static'))) + + with rosbag.Bag(out, 'w') as outbag: + if args.no_progress_bar is False: + progress = tqdm(total=input_bag.get_message_count()) + for topic, msg, t in input_bag: + # update the progress with a post fix + if args.no_progress_bar is False: + progress.update(1) + progress.set_postfix(time=t) + if topic == '/tf': + for tsm in tf_static_messages: + for tsmtr in tsm.message.transforms: + tsmtr.header.stamp = t + outbag.write('/tf', tsm.message, + t - rospy.Duration(0.1)) + outbag.write(topic, msg, t) + termcolor.cprint('=> Saved to {}'.format(out), 'green') + + +if __name__ == '__main__': + main() diff --git a/jsk_rosbag_tools/scripts/video_to_bag.py b/jsk_rosbag_tools/scripts/video_to_bag.py new file mode 100644 index 000000000..3fa660cd5 --- /dev/null +++ b/jsk_rosbag_tools/scripts/video_to_bag.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +import argparse +from pathlib import Path + +from jsk_rosbag_tools.video import video_to_bag + + +def main(): + parser = argparse.ArgumentParser( + description='Convert video to bag.') + parser.add_argument('inputvideo') + parser.add_argument('--out', '-o', type=str, + help='name of the output bag file', + default=None, metavar="output_file") + parser.add_argument('--topic-name', type=str, + default='/video/rgb/image_raw', + help='Converted topic name.') + parser.add_argument('--compress', action='store_true', + help='Compress Image flag.') + parser.add_argument('--no-progress-bar', action='store_true', + help="Don't show progress bar.") + args = parser.parse_args() + + video_path = Path(args.inputvideo) + if args.out is None: + args.out = video_path.with_suffix('.bag') + + outfile = Path(args.out) + pattern = str(video_path.parent / (video_path.stem + "_%i.bag")) + index = 0 + while outfile.exists(): + outfile = Path(pattern % index) + index += 1 + video_to_bag( + video_path, outfile, + args.topic_name, + compress=args.compress, + show_progress_bar=not args.no_progress_bar) + + +if __name__ == '__main__': + main() diff --git a/jsk_rosbag_tools/setup.py b/jsk_rosbag_tools/setup.py new file mode 100644 index 000000000..939174bc8 --- /dev/null +++ b/jsk_rosbag_tools/setup.py @@ -0,0 +1,12 @@ +from distutils.core import setup + +from catkin_pkg.python_setup import generate_distutils_setup +from setuptools import find_packages + + +d = generate_distutils_setup( + packages=find_packages('python'), + package_dir={'': 'python'}, +) + +setup(**d) diff --git a/jsk_rosbag_tools/tests/output/.gitignore b/jsk_rosbag_tools/tests/output/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/jsk_rosbag_tools/tests/output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py new file mode 100644 index 000000000..cc23d5f4b --- /dev/null +++ b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +import os.path as osp +import subprocess +import unittest + +import rospkg + + +PKG = 'jsk_rosbag_tools' +NAME = 'test_jsk_rosbag_tools' + + +class TestJSKRosBagTools(unittest.TestCase): + + def test_tf_static_to_tf(self): + rospack = rospkg.RosPack() + path = rospack.get_path('jsk_rosbag_tools') + video_bag_path = osp.join(path, 'samples', 'data', + '20220530173950_go_to_kitchen_rosbag.bag') + cmd = 'rosrun jsk_rosbag_tools tf_static_to_tf.py {} ' \ + '--no-progress-bar'.format(video_bag_path) + proc = subprocess.Popen(cmd, shell=True) + proc.wait() + + if proc.returncode != 0: + raise RuntimeError + + def test_merge(self): + rospack = rospkg.RosPack() + path = rospack.get_path('jsk_rosbag_tools') + + output_path = osp.join(path, 'tests', 'output', 'merged.bag') + audio_bag_path = osp.join(path, 'samples', 'data', + '2022-05-07-hello-test.bag') + cmd = 'rosrun jsk_rosbag_tools merge.py {} {} -o {}'\ + .format(audio_bag_path, audio_bag_path, output_path) + proc = subprocess.Popen(cmd, shell=True) + proc.wait() + + if proc.returncode != 0: + raise RuntimeError + + def test_bag_to_video_and_video_to_bag_and_compress(self): + rospack = rospkg.RosPack() + path = rospack.get_path('jsk_rosbag_tools') + + output_dir = osp.join(path, 'tests', 'output', 'audio') + audio_bag_path = osp.join(path, 'samples', 'data', + '2022-05-07-hello-test.bag') + + cmd = 'rosrun jsk_rosbag_tools bag_to_video.py {} -o {}'.format( + audio_bag_path, output_dir) + proc = subprocess.Popen(cmd, shell=True) + proc.wait() + + if proc.returncode != 0: + raise RuntimeError + + output_dir = osp.join(path, 'tests', 'output', 'video') + video_bag_path = osp.join(path, 'samples', 'data', + '20220530173950_go_to_kitchen_rosbag.bag') + cmd = 'rosrun jsk_rosbag_tools bag_to_video.py {} -o {}'.format( + video_bag_path, output_dir) + proc = subprocess.Popen(cmd, shell=True) + proc.wait() + + if proc.returncode != 0: + raise RuntimeError + + # video_to_bag.py test + video_path = osp.join( + output_dir, + 'head_camera--slash--rgb--slash--throttled' + '--slash--image_rect_color--slash--compressed-with-audio.mp4') + output_filename = osp.join(path, 'tests', 'output', 'video_to_bag.bag') + cmd = 'rosrun jsk_rosbag_tools video_to_bag.py {} ' \ + '--out {} --no-progress-bar'.format( + video_path, output_filename) + proc = subprocess.Popen(cmd, shell=True) + proc.wait() + + if proc.returncode != 0: + raise RuntimeError + + # compress_imgs.py test + cmd = 'rosrun jsk_rosbag_tools compress_imgs.py {} ' \ + '--no-progress-bar'.format(output_filename) + proc = subprocess.Popen(cmd, shell=True) + proc.wait() + + if proc.returncode != 0: + raise RuntimeError + + +if __name__ == '__main__': + import rostest + rostest.rosrun(PKG, NAME, TestJSKRosBagTools) diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test new file mode 100644 index 000000000..1130df8fa --- /dev/null +++ b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test @@ -0,0 +1,7 @@ + + + + + + From 7218ec9cdcfcb230ce5f4b4132f9e90b03292a0e Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 14:30:27 +0900 Subject: [PATCH 02/45] [jsk_rosbag_tools] Drop catkin virtualenv --- jsk_rosbag_tools/.gitignore | 1 - jsk_rosbag_tools/CMakeLists.txt | 33 ++---- jsk_rosbag_tools/package.xml | 24 ++--- .../python/jsk_rosbag_tools/__init__.py | 4 - .../python/jsk_rosbag_tools/cv.py | 3 +- .../jsk_rosbag_tools/cv_bridge_compat.py | 35 ------ .../python/jsk_rosbag_tools/cv_compat.py | 14 --- .../python/jsk_rosbag_tools/extract.py | 29 ++--- .../python/jsk_rosbag_tools/video.py | 100 +++++++++++------- jsk_rosbag_tools/requirements.in | 4 - jsk_rosbag_tools/scripts/bag_to_video.py | 2 +- 11 files changed, 88 insertions(+), 161 deletions(-) delete mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/cv_bridge_compat.py delete mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/cv_compat.py delete mode 100644 jsk_rosbag_tools/requirements.in diff --git a/jsk_rosbag_tools/.gitignore b/jsk_rosbag_tools/.gitignore index 486fd4ec6..f935021a8 100644 --- a/jsk_rosbag_tools/.gitignore +++ b/jsk_rosbag_tools/.gitignore @@ -1,2 +1 @@ -requirements.txt !.gitignore diff --git a/jsk_rosbag_tools/CMakeLists.txt b/jsk_rosbag_tools/CMakeLists.txt index 5fd30db9c..e9a6d698c 100644 --- a/jsk_rosbag_tools/CMakeLists.txt +++ b/jsk_rosbag_tools/CMakeLists.txt @@ -3,7 +3,6 @@ project(jsk_rosbag_tools) find_package( catkin REQUIRED - catkin_virtualenv ) catkin_python_setup() @@ -12,23 +11,11 @@ catkin_package( CATKIN_DEPENDS ) -if (${catkin_virtualenv_VERSION} VERSION_LESS "0.6.1") - message(WARNING "Please install catkin_virtualenv>=0.6.1.") - message(WARNING "Current catkin_virtualen version is ${catkin_virtualenv_VERSION}") -else() - catkin_generate_virtualenv( - INPUT_REQUIREMENTS requirements.in - PYTHON_INTERPRETER python3 - USE_SYSTEM_PACKAGES TRUE - ISOLATE_REQUIREMENTS FALSE - CHECK_VENV FALSE - ) - file(GLOB SCRIPTS_FILES scripts/*) - catkin_install_python( - PROGRAMS ${SCRIPTS_FILES} - DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} - ) -endif() +file(GLOB SCRIPTS_FILES scripts/*) +catkin_install_python( + PROGRAMS ${SCRIPTS_FILES} + DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +) catkin_download(download_audio_data https://drive.google.com/uc?export=download&id=1rFZYoFjLqIWjEe0DaNiL3k9893m31nu7 @@ -50,12 +37,8 @@ install(FILES requirements.in requirements.txt if(CATKIN_ENABLE_TESTING) - find_package(catkin REQUIRED COMPONENTS catkin_virtualenv roslint rostest) + find_package(catkin REQUIRED COMPONENTS roslint rostest) - catkin_generate_virtualenv( - INPUT_REQUIREMENTS requirements.in - PYTHON_INTERPRETER python3 - ) set(python_test_scripts tests/test_jsk_rosbag_tools.py ) @@ -67,7 +50,5 @@ if(CATKIN_ENABLE_TESTING) catkin_install_python(PROGRAMS ${python_test_scripts} DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) - add_rostest(tests/test_jsk_rosbag_tools.test - DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv - ) + add_rostest(tests/test_jsk_rosbag_tools.test) endif() diff --git a/jsk_rosbag_tools/package.xml b/jsk_rosbag_tools/package.xml index 694489705..a42f6c319 100644 --- a/jsk_rosbag_tools/package.xml +++ b/jsk_rosbag_tools/package.xml @@ -9,27 +9,19 @@ Iori Yanokura catkin - python3-catkin-pkg-modules + python-catkin-pkg-modules - catkin_virtualenv cv_bridge - cv_bridge_python3 sensor_msgs - python3-empy - python3-gnupg - python3-numpy - python3-pycryptodome - python3-rospkg-modules - python3-setuptools - python3-termcolor - python3-tqdm - python3-yaml - libsndfile1-dev + python-moviepy-pip + python-numpy + python-rospkg-modules + python-setuptools + python-termcolor + python-tqdm + python-yaml roslint rostest - - requirements.txt - diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/__init__.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/__init__.py index cace884b2..9c0fa90a1 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/__init__.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/__init__.py @@ -1,5 +1 @@ # flake8: noqa -from jsk_rosbag_tools.cv_bridge_compat import cv_bridge -from jsk_rosbag_tools.cv_bridge_compat import CvBridge - -from jsk_rosbag_tools.cv_compat import cv2 diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py index 2eb3ab049..bcc2532d6 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py @@ -1,11 +1,10 @@ import struct import cv2 +import cv_bridge import numpy as np import sensor_msgs.msg -from jsk_rosbag_tools.cv_bridge_compat import cv_bridge - _bridge = cv_bridge.CvBridge() diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_bridge_compat.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_bridge_compat.py deleted file mode 100644 index 78c2b8241..000000000 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_bridge_compat.py +++ /dev/null @@ -1,35 +0,0 @@ -import importlib -import os -import sys - - -# cv_bridge_python3 import -if os.environ['ROS_PYTHON_VERSION'] == '3': - cv_bridge = importlib.import_module("cv_bridge") - CvBridge = getattr(cv_bridge, 'CvBridge') -else: - ws_python3_paths = [p for p in sys.path if 'devel/lib/python3' in p] - if len(ws_python3_paths) == 0: - # search cv_bridge in workspace and append - ws_python2_paths = [ - p for p in sys.path if 'devel/lib/python2.7' in p] - for ws_python2_path in ws_python2_paths: - ws_python3_path = ws_python2_path.replace('python2.7', 'python3') - if os.path.exists(os.path.join(ws_python3_path, 'cv_bridge')): - ws_python3_paths.append(ws_python3_path) - if len(ws_python3_paths) == 0: - opt_python3_path = '/opt/ros/{}/lib/python3/dist-packages'.format( - os.getenv('ROS_DISTRO')) - sys.path = [opt_python3_path] + sys.path - cv_bridge = importlib.import_module("cv_bridge") - CvBridge = getattr(cv_bridge, 'CvBridge') - sys.path.remove(opt_python3_path) - else: - sys.path = [ws_python3_paths[0]] + sys.path - cv_bridge = importlib.import_module("cv_bridge") - CvBridge = importlib.import_module("cv_bridge.CvBridge") - CvBridge = getattr(cv_bridge, 'CvBridge') - sys.path.remove(ws_python3_paths[0]) - else: - cv_bridge = importlib.import_module("cv_bridge") - CvBridge = getattr(cv_bridge, 'CvBridge') diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_compat.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_compat.py deleted file mode 100644 index 68b4457f0..000000000 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv_compat.py +++ /dev/null @@ -1,14 +0,0 @@ -import importlib -import os -import sys - - -# OpenCV import for python3 -if os.environ['ROS_PYTHON_VERSION'] == '3': - cv2 = importlib.import_module("cv2") -else: - sys.path.remove('/opt/ros/{}/lib/python2.7/dist-packages' - .format(os.getenv('ROS_DISTRO'))) - cv2 = importlib.import_module("cv2") - sys.path.append('/opt/ros/{}/lib/python2.7/dist-packages' - .format(os.getenv('ROS_DISTRO'))) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py index cd50c66da..31c330c52 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py @@ -1,10 +1,8 @@ import os import numpy as np -from pydub import AudioSegment -from pydub import effects import rosbag -import soundfile as sf +from scipy.io.wavfile import write as wav_write from jsk_rosbag_tools.cv import compressed_format from jsk_rosbag_tools.cv import decompresse_imgmsg @@ -101,8 +99,9 @@ def extract_audio(bag_filepath, audio_info_topic_name=None, samplerate=44100, channels=1, - normalize=True, overwrite=True): + if os.path.exists(wav_outpath) and overwrite is False: + raise FileExistsError('{} file already exists.'.format(wav_outpath)) topic_dict = get_topic_dict(bag_filepath) if topic_name not in topic_dict: return @@ -116,23 +115,17 @@ def extract_audio(bag_filepath, channels = audio_info.channels bag = rosbag.Bag(bag_filepath) - with sf.SoundFile(wav_outpath, mode='w' if overwrite else 'x', - samplerate=samplerate, - channels=channels, - format='wav') as f: - for _, msg, _ in bag.read_messages(topics=[topic_name]): - if msg._type == 'audio_common_msgs/AudioData': - audio_buffer = np.frombuffer(msg.data, dtype='int16') - audio_buffer = audio_buffer.reshape(-1, channels) - f.write(audio_buffer) + audio_buffer = [] + for _, msg, _ in bag.read_messages(topics=[topic_name]): + if msg._type == 'audio_common_msgs/AudioData': + buf = np.frombuffer(msg.data, dtype='int16') + buf = buf.reshape(-1, channels) + audio_buffer.append(buf) + audio_buffer = np.concatenate(audio_buffer, axis=0) + wav_write(wav_outpath, rate=samplerate, data=audio_buffer) valid = os.stat(wav_outpath).st_size != 0 if valid is False: return False - if normalize: - sound = AudioSegment.from_file(wav_outpath) - normalized_sound = effects.normalize(sound, 1.0) - normalized_sound.export(wav_outpath, format='wav') - return True diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py index 0415ef494..c039aa896 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py @@ -3,24 +3,67 @@ import datetime import math import os.path as osp +import re import shutil import subprocess +import sys import tempfile +import wave import audio_common_msgs.msg +import cv2 +from moviepy.editor import VideoFileClip import numpy as np -from pydub.utils import mediainfo import rosbag import rospy -import skvideo.io -import soundfile as sf from tqdm import tqdm from jsk_rosbag_tools.cv import img_to_msg -from jsk_rosbag_tools.cv_compat import cv2 from jsk_rosbag_tools.merge import merge_bag +def mediainfo(filepath): + prober = 'ffprobe' + command_args = [ + "-v", "quiet", + "-show_format", + "-show_streams", + filepath + ] + + command = [prober, '-of', 'old'] + command_args + res = subprocess.Popen(command, stdout=subprocess.PIPE) + output = res.communicate()[0].decode("utf-8") + + if res.returncode != 0: + command = [prober] + command_args + output = subprocess.Popen( + command, + stdout=subprocess.PIPE).communicate()[0].decode("utf-8") + rgx = re.compile(r"(?:(?P.*?):)?(?P.*?)\=(?P.*?)$") + info = {} + if sys.platform == 'win32': + output = output.replace("\r", "") + for line in output.split("\n"): + # print(line) + mobj = rgx.match(line) + + if mobj: + # print(mobj.groups()) + inner_dict, key, value = mobj.groups() + + if inner_dict: + try: + info[inner_dict] + except KeyError: + info[inner_dict] = {} + info[inner_dict][key] = value + else: + info[key] = value + + return info + + def nsplit(xlst, n): total_n = len(xlst) d = int((total_n + n - 1) / n) @@ -36,43 +79,15 @@ def get_video_duration(video_path): video_path = str(video_path) if not osp.exists(video_path): raise OSError("{} not exists".format(video_path)) - metadata = skvideo.io.ffprobe(video_path) - return float(metadata['video']['@duration']) - - -def get_video_avg_frame_rate(video_path): - video_path = str(video_path) - if not osp.exists(video_path): - raise OSError("{} not exists".format(video_path)) - metadata = skvideo.io.ffprobe(video_path) - a, b = metadata['video']['@avg_frame_rate'].split('/') - a = int(a) - b = int(b) - return a / b - - -def get_video_creation_time(video_path): - metadata = skvideo.io.ffprobe(video_path) - tag_dict = {} - for tag in metadata['video']['tag']: - tag_dict[tag['@key']] = tag['@value'] - if 'creation_time' not in tag_dict: - return None - creation_time = tag_dict['creation_time'] - created_at = datetime.datetime.strptime( - creation_time, '%Y-%m-%dT%H:%M:%S.%fZ') - return created_at + return VideoFileClip(video_path).duration def get_video_n_frame(video_path): video_path = str(video_path) if not osp.exists(video_path): raise OSError("{} not exists".format(video_path)) - metadata = skvideo.io.ffprobe(video_path) - if '@nb_frames' not in metadata['video']: - fps = get_video_avg_frame_rate(video_path) - return int(fps * get_video_duration(video_path)) - return int(metadata['video']['@nb_frames']) + clip = VideoFileClip(video_path) + return int(clip.duration * clip.fps) def load_frame(video_path, start=0.0, duration=-1, @@ -108,10 +123,7 @@ def video_to_bag(video_filepath, bag_output_filepath, base_unixtime=None, show_progress_bar=True): if base_unixtime is None: - base_unixtime = get_video_creation_time(video_filepath) - if base_unixtime is None: - base_unixtime = datetime.datetime.now() - base_unixtime = base_unixtime.timestamp() + base_unixtime = datetime.datetime.now().timestamp() topic_name = topic_name.lstrip('/compressed') if compress is True: @@ -143,7 +155,15 @@ def video_to_bag(video_filepath, bag_output_filepath, stderr=subprocess.PIPE) try: - data, sample_rate = sf.read(wav_filepath, dtype='int16') + wf = wave.open(wav_filepath, mode='rb') + sample_rate = wf.getframerate() + wf.rewind() + buf = wf.readframes(-1) + if wf.getsampwidth() == 2: + data = np.frombuffer(buf, dtype='int16') + elif wf.getsampwidth() == 4: + data = np.frombuffer(buf, dtype='int32') + data = data.reshape(-1, wf.getnchannels()) media_info = mediainfo(wav_filepath) except RuntimeError: extract_audio = False diff --git a/jsk_rosbag_tools/requirements.in b/jsk_rosbag_tools/requirements.in deleted file mode 100644 index fecff2535..000000000 --- a/jsk_rosbag_tools/requirements.in +++ /dev/null @@ -1,4 +0,0 @@ -moviepy -pydub -scikit-video -soundfile diff --git a/jsk_rosbag_tools/scripts/bag_to_video.py b/jsk_rosbag_tools/scripts/bag_to_video.py index 745225ebe..adbbd9cf6 100644 --- a/jsk_rosbag_tools/scripts/bag_to_video.py +++ b/jsk_rosbag_tools/scripts/bag_to_video.py @@ -5,10 +5,10 @@ import os import sys +import cv2 import moviepy.editor as mp from moviepy.video.io.ffmpeg_writer import FFMPEG_VideoWriter -from jsk_rosbag_tools import cv2 from jsk_rosbag_tools.extract import extract_audio from jsk_rosbag_tools.extract import extract_image_topic from jsk_rosbag_tools.extract import get_image_topic_names From 1285a82eb0c8152dcf9426ac0ce005832a35be4b Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 14:31:00 +0900 Subject: [PATCH 03/45] [jsk_rosbag_tools] Set version to 2.2.11 --- jsk_rosbag_tools/package.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/package.xml b/jsk_rosbag_tools/package.xml index a42f6c319..10d5c62e6 100644 --- a/jsk_rosbag_tools/package.xml +++ b/jsk_rosbag_tools/package.xml @@ -1,7 +1,7 @@ jsk_rosbag_tools - 0.0.1 + 2.2.11 The rosbag tools BSD From 1a67212509ee6ede8ceb48e3e9a84bdf102c96ce Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 14:46:39 +0900 Subject: [PATCH 04/45] [jsk_rosbag_tools] Modified sample bagfile to reduce test time --- jsk_rosbag_tools/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/CMakeLists.txt b/jsk_rosbag_tools/CMakeLists.txt index e9a6d698c..84e90e216 100644 --- a/jsk_rosbag_tools/CMakeLists.txt +++ b/jsk_rosbag_tools/CMakeLists.txt @@ -27,7 +27,7 @@ catkin_download(download_video_data https://drive.google.com/uc?export=download&id=1v4YNOHnHYxLOty1lYR2R6lfNF0itCwK7 DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/samples/data FILENAME 20220530173950_go_to_kitchen_rosbag.bag - MD5 d6a146f06e1a62430f1933704cc7a740 + MD5 d51fa8aeacd36f7aaa1597b67bd9ffdf ) add_custom_target(download ALL DEPENDS download_audio_data download_video_data) From cdc7f5c8d1b6120bc0326570f483007f4bf751ca Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 14:47:11 +0900 Subject: [PATCH 05/45] [jsk_rosbag_tools] Set time-limit to 360.0 --- jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test index 1130df8fa..b4e61b4e8 100644 --- a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test +++ b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test @@ -2,6 +2,6 @@ + time-limit="360.0" /> From fd1d92c69cc12090d7b29c8281307ce970f79ae7 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 17:33:42 +0900 Subject: [PATCH 06/45] [jsk_rosbag_tools] Add makedirs library for python2 --- .../python/jsk_rosbag_tools/makedirs.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/makedirs.py diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/makedirs.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/makedirs.py new file mode 100644 index 000000000..0edf55ab7 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/makedirs.py @@ -0,0 +1,32 @@ +import os +import sys + + +PY3 = (sys.version_info[0] == 3) +PY2 = not PY3 + + +def makedirs(name, mode=0o777, exist_ok=True): + """An wrapper of os.makedirs that accepts exist_ok. + + Parameters + ---------- + name : str + path of directory + exist_ok : bool + if True, accepts the existence of the directory. + + Examples + -------- + >>> from eos import makedirs + >>> makedirs('/tmp/result_directory') + """ + name = str(name) + if PY2: + try: + os.makedirs(name, mode) + except OSError: + if not (exist_ok and os.path.isdir(name)): + raise OSError + else: + os.makedirs(name, mode, exist_ok=exist_ok) From bf8fb8b0a2529f7904af9002d952213c8dd3f829 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 17:34:07 +0900 Subject: [PATCH 07/45] [jsk_rosbag_tools] Add topic_name_to_file_name --- .../python/jsk_rosbag_tools/topic_name_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/topic_name_utils.py diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/topic_name_utils.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/topic_name_utils.py new file mode 100644 index 000000000..16d190e3a --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/topic_name_utils.py @@ -0,0 +1,7 @@ +def topic_name_to_file_name(topic_name): + if topic_name[0] == '/': + replaced_topic_name = topic_name[1:] + else: + replaced_topic_name = topic_name + replaced_topic_name = replaced_topic_name.replace('/', '--slash--') + return replaced_topic_name From aa6058b1966ff1e2de14d89f16334d8b9b7b5503 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 17:34:23 +0900 Subject: [PATCH 08/45] [jsk_rosbag_tools] Seperate bag_to_video function as library --- .../python/jsk_rosbag_tools/bag_to_video.py | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py new file mode 100644 index 000000000..914ad91b3 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -0,0 +1,118 @@ +import datetime +import os +import os.path as osp +import sys +import tempfile + +import cv2 +import moviepy.editor as mp +from moviepy.video.io.ffmpeg_writer import FFMPEG_VideoWriter + +from jsk_rosbag_tools.extract import extract_audio +from jsk_rosbag_tools.extract import extract_image_topic +from jsk_rosbag_tools.extract import get_image_topic_names +from jsk_rosbag_tools.makedirs import makedirs +from jsk_rosbag_tools.topic_name_utils import topic_name_to_file_name + + +def bag_to_video(input_bagfile, + output_filepath=None, + output_dirpath=None, + image_topic=None, + image_topics=None, + fps=30, + samplerate=16000, + channels=1, + audio_topic='/audio'): + """Create video from rosbag file. + + Specify only either output_filepath or output_dirpath. + If output_filepath is specified, specify image_topic. + If output_dirpath is specified, image_topics can be specified. + If image_topic_names is None, make all color images into video. + + """ + if not os.path.exists(input_bagfile): + print('Input bagfile {} not exists.'.format(input_bagfile)) + sys.exit(1) + + if output_filepath is not None and output_dirpath is not None: + raise ValueError( + 'Specify only either output_filepath or output_dirpath.') + + output_filepaths = [] + target_image_topics = [] + if output_filepath is not None: + if image_topic is None: + raise ValueError( + 'If output_filepath is specified, specify image_topic.') + output_filepaths.append(output_filepath) + target_image_topics.append(image_topic) + wav_outpath = tempfile.NamedTemporaryFile(suffix='.wav').name + else: + # output_dirpath is specified case. + if image_topics is None: + image_topics = get_image_topic_names( + input_bagfile, rgb_only=True) + target_image_topics = image_topics + + for image_topic in target_image_topics: + output_filepaths.append( + osp.join( + output_dirpath, + topic_name_to_file_name(image_topic) + '.mp4')) + wav_outpath = osp.join(output_dirpath, '{}.wav'.format( + topic_name_to_file_name(audio_topic))) + + audio_exists = extract_audio(input_bagfile, wav_outpath, + samplerate=samplerate, + channels=channels, + topic_name=audio_topic) + + dt = 1.0 / fps + for image_topic, output_filepath in zip(target_image_topics, + output_filepaths): + makedirs(osp.dirname(output_filepath)) + if audio_exists: + tmp_videopath = tempfile.NamedTemporaryFile(suffix='.mp4').name + else: + tmp_videopath = output_filepath + + images = extract_image_topic(input_bagfile, image_topic) + + # remove 0 time stamp + stamp = 0.0 + while stamp == 0.0: + stamp, _, img, _ = next(images) + start_stamp = stamp + + creation_time = datetime.datetime.utcfromtimestamp(start_stamp) + time_format = '%y-%m-%d %h:%M:%S' + writer = FFMPEG_VideoWriter( + tmp_videopath, + (img.shape[1], img.shape[0]), + fps, logfile=None, + ffmpeg_params=[ + '-metadata', + 'creation_time={}'.format( + creation_time.strftime(time_format)), + ]) + + current_time = 0.0 + cur_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + for stamp, _, bgr_img, _ in images: + rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) + aligned_stamp = stamp - start_stamp + while current_time < aligned_stamp: + current_time += dt + writer.write_frame(cur_img) + cur_img = rgb_img + writer.write_frame(cur_img) + current_time += dt + writer.close() + + if audio_exists: + clip_output = mp.VideoFileClip(tmp_videopath).subclip().\ + set_audio(mp.AudioFileClip(wav_outpath)) + clip_output.write_videofile( + output_filepath) From a6169b32cb883d7cf47c4d6e569df1543302450b Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 17:34:35 +0900 Subject: [PATCH 09/45] [jsk_rosbag_tools] Use library's function --- jsk_rosbag_tools/scripts/bag_to_video.py | 121 ++++++----------------- 1 file changed, 31 insertions(+), 90 deletions(-) diff --git a/jsk_rosbag_tools/scripts/bag_to_video.py b/jsk_rosbag_tools/scripts/bag_to_video.py index adbbd9cf6..7782fe1c8 100644 --- a/jsk_rosbag_tools/scripts/bag_to_video.py +++ b/jsk_rosbag_tools/scripts/bag_to_video.py @@ -1,23 +1,18 @@ #!/usr/bin/env python import argparse -import datetime -import os -import sys +import os.path as osp -import cv2 -import moviepy.editor as mp -from moviepy.video.io.ffmpeg_writer import FFMPEG_VideoWriter - -from jsk_rosbag_tools.extract import extract_audio -from jsk_rosbag_tools.extract import extract_image_topic -from jsk_rosbag_tools.extract import get_image_topic_names +from jsk_rosbag_tools.bag_to_video import bag_to_video def main(): parser = argparse.ArgumentParser(description='rosbag to video') parser.add_argument('--out', '-o', default='', - help='output directory path') + help='output directory path or filename. ' + 'If more than one --image-topic are specified, ' + 'this will be interpreted as a directory name. ' + 'Otherwise this is the file name.') parser.add_argument('--fps', default=30, type=int) parser.add_argument('--samplerate', '-r', type=int, help='sampling rate', @@ -25,91 +20,37 @@ def main(): parser.add_argument('--channels', type=int, default=1, help='number of input channels') parser.add_argument('--audio-topic', type=str, default='/audio') - parser.add_argument('--image-topics', type=str, + parser.add_argument('--image-topic', type=str, default=[], nargs='+', help='Topic name to extract.') parser.add_argument('input_bagfile') args = parser.parse_args() - input_bagfile = args.input_bagfile - if not os.path.exists(input_bagfile): - print('Input bagfile {} not exists.'.format(input_bagfile)) - sys.exit(1) - if len(args.out) == 0: - basename = os.path.basename(input_bagfile) - dirname = os.path.dirname(input_bagfile) - filename, _ = os.path.splitext(basename) - out = os.path.join(dirname, filename) - else: - out = args.out - os.makedirs(out, mode=0o777, exist_ok=True) - - fps = args.fps - dt = 1.0 / fps - - wav_outpath = os.path.join(out, 'audio.wav') - audio_exists = extract_audio(input_bagfile, wav_outpath, - samplerate=args.samplerate, - channels=args.channels, - topic_name=args.audio_topic) - - candidates_topic_names = get_image_topic_names( - input_bagfile, rgb_only=True) - if args.image_topics is None: - topic_names = candidates_topic_names + args.out = osp.join( + osp.dirname(args.input_bagfile), + osp.splitext(osp.basename(args.input_bagfile))[0]) + input_bagfile = args.input_bagfile + image_topic = None + image_topics = None + output_filepath = None + output_dirpath = None + if len(args.image_topic) == 1: + image_topic = args.image_topic[0] + output_filepath = args.out + elif len(args.image_topic) > 1: + image_topics = args.image_topic + output_dirpath = args.out else: - topic_names = [tn for tn in args.image_topics - if tn in candidates_topic_names] - - for topic_name in topic_names: - images = extract_image_topic(input_bagfile, topic_name) - - # remove 0 time stamp - stamp = 0.0 - while stamp == 0.0: - stamp, _, img, _ = next(images) - start_stamp = stamp - - if topic_name[0] == '/': - replaced_topic_name = topic_name[1:] - else: - replaced_topic_name = topic_name - replaced_topic_name = replaced_topic_name.replace('/', '--slash--') - output_videopath = os.path.join( - out, "{}.mp4".format(replaced_topic_name)) - - creation_time = datetime.datetime.utcfromtimestamp(start_stamp) - time_format = '%y-%m-%d %h:%M:%S' - writer = FFMPEG_VideoWriter( - output_videopath, - (img.shape[1], img.shape[0]), - fps, logfile=None, - ffmpeg_params=[ - '-metadata', - 'creation_time={}'.format( - creation_time.strftime(time_format)), - ]) - - current_time = 0.0 - cur_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - for stamp, _, bgr_img, _ in images: - rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) - aligned_stamp = stamp - start_stamp - while current_time < aligned_stamp: - current_time += dt - writer.write_frame(cur_img) - cur_img = rgb_img - writer.write_frame(cur_img) - current_time += dt - writer.close() - - if audio_exists: - output_video_with_audio_path = os.path.join( - out, "{}-with-audio.mp4".format(replaced_topic_name)) - clip_output = mp.VideoFileClip(output_videopath).subclip().\ - set_audio(mp.AudioFileClip(wav_outpath)) - clip_output.write_videofile( - output_video_with_audio_path) + output_dirpath = args.out + bag_to_video(input_bagfile, + output_filepath=output_filepath, + output_dirpath=output_dirpath, + image_topic=image_topic, + image_topics=image_topics, + fps=args.fps, + samplerate=args.samplerate, + channels=args.channels, + audio_topic=args.audio_topic) if __name__ == '__main__': From d91d62898e2b26cfdafabdc9fadf55c872238a65 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 17:58:16 +0900 Subject: [PATCH 10/45] [jsk_rosbag_tools] Import AudioFileClip and VideoFileClip --- jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index 914ad91b3..02a976a10 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -5,7 +5,8 @@ import tempfile import cv2 -import moviepy.editor as mp +from moviepy.editor import AudioFileClip +from moviepy.editor import VideoFileClip from moviepy.video.io.ffmpeg_writer import FFMPEG_VideoWriter from jsk_rosbag_tools.extract import extract_audio @@ -112,7 +113,7 @@ def bag_to_video(input_bagfile, writer.close() if audio_exists: - clip_output = mp.VideoFileClip(tmp_videopath).subclip().\ - set_audio(mp.AudioFileClip(wav_outpath)) + clip_output = VideoFileClip(tmp_videopath).subclip().\ + set_audio(AudioFileClip(wav_outpath)) clip_output.write_videofile( output_filepath) From 0684b85e130f3689814ed83807bda1fbc8948937 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 18:06:58 +0900 Subject: [PATCH 11/45] [jsk_rosbag_tools] Fixed bag to video docs --- jsk_rosbag_tools/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jsk_rosbag_tools/README.md b/jsk_rosbag_tools/README.md index b2e82d912..44377e67c 100644 --- a/jsk_rosbag_tools/README.md +++ b/jsk_rosbag_tools/README.md @@ -9,7 +9,7 @@ Create video from rosbag. ### Usage ``` -usage: bag_to_video.py [-h] [--out OUT] [--fps FPS] [--samplerate SAMPLERATE] [--channels CHANNELS] [--audio-topic AUDIO_TOPIC] [--image-topics IMAGE_TOPICS [IMAGE_TOPICS ...]] input_bagfile +usage: bag_to_video.py [-h] [--out OUT] [--fps FPS] [--samplerate SAMPLERATE] [--channels CHANNELS] [--audio-topic AUDIO_TOPIC] [--image-topic IMAGE_TOPIC [IMAGE_TOPIC ...]] input_bagfile rosbag to video @@ -18,13 +18,16 @@ positional arguments: optional arguments: -h, --help show this help message and exit - --out OUT, -o OUT output directory path + --out OUT, -o OUT output directory path or filename. + If more than one --image-topic are specified, + this will be interpreted as a directory name. + Otherwise this is the file name. --fps FPS --samplerate SAMPLERATE, -r SAMPLERATE sampling rate --channels CHANNELS number of input channels --audio-topic AUDIO_TOPIC - --image-topics IMAGE_TOPICS [IMAGE_TOPICS ...] + --image-topic IMAGE_TOPIC [IMAGE_TOPIC ...] Topic name to extract. ``` From b185faa16ba28f24be628668ff50f3eb8ea067e2 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 18:08:35 +0900 Subject: [PATCH 12/45] [jsk_rosbag_tools] Fixed sample docs --- jsk_rosbag_tools/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsk_rosbag_tools/README.md b/jsk_rosbag_tools/README.md index 44377e67c..9e37076ad 100644 --- a/jsk_rosbag_tools/README.md +++ b/jsk_rosbag_tools/README.md @@ -36,8 +36,8 @@ optional arguments: ``` rosrun jsk_rosbag_tools bag_to_video.py $(rospack find jsk_rosbag_tools)/samples/data/20220530173950_go_to_kitchen_rosbag.bag \ --samplerate 16000 --channels 1 --audio-topic /audio \ - --image-topics /head_camera/rgb/throttled/image_rect_color/compressed \ - -o /tmp/output_bag + --image-topic /head_camera/rgb/throttled/image_rect_color/compressed \ + -o /tmp/20220530173950_go_to_kitchen_rosbag.mp4 ``` ## video_to_bag.py From c8e4efb13c4c2d86f8c998ebe83bde041d46b03c Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 18:23:44 +0900 Subject: [PATCH 13/45] [jsk_rosbag_tools] chmod u+x --- jsk_rosbag_tools/scripts/bag_to_video.py | 0 jsk_rosbag_tools/scripts/compress_imgs.py | 0 jsk_rosbag_tools/scripts/merge.py | 0 jsk_rosbag_tools/scripts/tf_static_to_tf.py | 0 jsk_rosbag_tools/scripts/video_to_bag.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 jsk_rosbag_tools/scripts/bag_to_video.py mode change 100644 => 100755 jsk_rosbag_tools/scripts/compress_imgs.py mode change 100644 => 100755 jsk_rosbag_tools/scripts/merge.py mode change 100644 => 100755 jsk_rosbag_tools/scripts/tf_static_to_tf.py mode change 100644 => 100755 jsk_rosbag_tools/scripts/video_to_bag.py diff --git a/jsk_rosbag_tools/scripts/bag_to_video.py b/jsk_rosbag_tools/scripts/bag_to_video.py old mode 100644 new mode 100755 diff --git a/jsk_rosbag_tools/scripts/compress_imgs.py b/jsk_rosbag_tools/scripts/compress_imgs.py old mode 100644 new mode 100755 diff --git a/jsk_rosbag_tools/scripts/merge.py b/jsk_rosbag_tools/scripts/merge.py old mode 100644 new mode 100755 diff --git a/jsk_rosbag_tools/scripts/tf_static_to_tf.py b/jsk_rosbag_tools/scripts/tf_static_to_tf.py old mode 100644 new mode 100755 diff --git a/jsk_rosbag_tools/scripts/video_to_bag.py b/jsk_rosbag_tools/scripts/video_to_bag.py old mode 100644 new mode 100755 From 49d0f518a03de41b9a1679bda786bdb90c02c6ee Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 18:28:06 +0900 Subject: [PATCH 14/45] [jsk_rosbag_tools] Include AudioFileClip and VideoFileClip to prevent calling pygame.init() --- jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index 02a976a10..af27adea4 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -5,8 +5,8 @@ import tempfile import cv2 -from moviepy.editor import AudioFileClip -from moviepy.editor import VideoFileClip +from moviepy.audio.io.AudioFileClip import AudioFileClip +from moviepy.video.io.VideoFileClip import VideoFileClip from moviepy.video.io.ffmpeg_writer import FFMPEG_VideoWriter from jsk_rosbag_tools.extract import extract_audio From 5566452815235ac75a50c003ea23320eca438e24 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 19:22:36 +0900 Subject: [PATCH 15/45] [jsk_rosbag_tools] Makedirs in extract_audio --- jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py index 31c330c52..e99066332 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py @@ -1,4 +1,5 @@ import os +import os.path as osp import numpy as np import rosbag @@ -8,6 +9,7 @@ from jsk_rosbag_tools.cv import decompresse_imgmsg from jsk_rosbag_tools.cv import msg_to_img from jsk_rosbag_tools.info import get_topic_dict +from jsk_rosbag_tools.makedirs import makedirs def get_image_topic_names(bag_filepath, @@ -122,6 +124,8 @@ def extract_audio(bag_filepath, buf = buf.reshape(-1, channels) audio_buffer.append(buf) audio_buffer = np.concatenate(audio_buffer, axis=0) + + makedirs(osp.dirname(wav_outpath)) wav_write(wav_outpath, rate=samplerate, data=audio_buffer) valid = os.stat(wav_outpath).st_size != 0 From 40ad381577d3b429ddc01e1c3ed23b4b2f11c4b3 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 19:29:31 +0900 Subject: [PATCH 16/45] [jsk_rosbag_tools] Add bag_to_audio.py --- jsk_rosbag_tools/README.md | 31 +++++++++++++ jsk_rosbag_tools/scripts/bag_to_audio.py | 44 +++++++++++++++++++ .../tests/test_jsk_rosbag_tools.py | 15 ++++++- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100755 jsk_rosbag_tools/scripts/bag_to_audio.py diff --git a/jsk_rosbag_tools/README.md b/jsk_rosbag_tools/README.md index 9e37076ad..3d042aca6 100644 --- a/jsk_rosbag_tools/README.md +++ b/jsk_rosbag_tools/README.md @@ -40,6 +40,37 @@ rosrun jsk_rosbag_tools bag_to_video.py $(rospack find jsk_rosbag_tools)/samples -o /tmp/20220530173950_go_to_kitchen_rosbag.mp4 ``` +## bag_to_audio.py + +Create audio file from rosbag. + +### Usage + +``` +usage: bag_to_audio.py [-h] [--out OUT] [--samplerate SAMPLERATE] [--channels CHANNELS] [--audio-topic AUDIO_TOPIC] input_bagfile + +rosbag to audio + +positional arguments: + input_bagfile + +optional arguments: + -h, --help show this help message and exit + --out OUT, -o OUT output filename. If `--audio-topic`_info is exists, you don't have to specify samplerate and channels. + --samplerate SAMPLERATE, -r SAMPLERATE + sampling rate + --channels CHANNELS number of input channels + --audio-topic AUDIO_TOPIC +``` + +### Example + +``` +rosrun jsk_rosbag_tools bag_to_audio.py $(rospack find jsk_rosbag_tools)/samples/data/20220530173950_go_to_kitchen_rosbag.bag \ + --samplerate 16000 --channels 1 --audio-topic /audio \ + -o /tmp/20220530173950_go_to_kitchen_rosbag.wav +``` + ## video_to_bag.py Convert video file to bagfile. diff --git a/jsk_rosbag_tools/scripts/bag_to_audio.py b/jsk_rosbag_tools/scripts/bag_to_audio.py new file mode 100755 index 000000000..c11141365 --- /dev/null +++ b/jsk_rosbag_tools/scripts/bag_to_audio.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +import argparse +import os.path as osp + +import termcolor + +from jsk_rosbag_tools.extract import extract_audio + + +def main(): + parser = argparse.ArgumentParser(description='rosbag to audio') + parser.add_argument('--out', '-o', default='', + help='output filename. ' + 'If `--audio-topic`_info is exists, ' + "you don't have to specify samplerate and channels.") + parser.add_argument('--samplerate', '-r', type=int, help='sampling rate', + default='16000') + parser.add_argument('--channels', + type=int, default=1, help='number of input channels') + parser.add_argument('--audio-topic', type=str, default='/audio') + parser.add_argument('input_bagfile') + args = parser.parse_args() + + if len(args.out) == 0: + args.out = osp.join( + osp.dirname(args.input_bagfile), + osp.splitext(osp.basename(args.input_bagfile))[0]) + + audio_exists = extract_audio( + args.input_bagfile, args.out, + samplerate=args.samplerate, + channels=args.channels, + topic_name=args.audio_topic) + if audio_exists is False: + termcolor.cprint( + '=> Audio topic is not exists', 'red') + else: + termcolor.cprint( + '=> Audio saved to {}'.format(args.out), 'green') + + +if __name__ == '__main__': + main() diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py index cc23d5f4b..0fe05c23f 100644 --- a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py +++ b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py @@ -13,6 +13,19 @@ class TestJSKRosBagTools(unittest.TestCase): + def test_bag_to_audio(self): + rospack = rospkg.RosPack() + path = rospack.get_path('jsk_rosbag_tools') + video_bag_path = osp.join(path, 'samples', 'data', + '20220530173950_go_to_kitchen_rosbag.bag') + cmd = 'rosrun jsk_rosbag_tools bag_to_audio.py {}'.format( + video_bag_path) + proc = subprocess.Popen(cmd, shell=True) + proc.wait() + + if proc.returncode != 0: + raise RuntimeError + def test_tf_static_to_tf(self): rospack = rospkg.RosPack() path = rospack.get_path('jsk_rosbag_tools') @@ -72,7 +85,7 @@ def test_bag_to_video_and_video_to_bag_and_compress(self): video_path = osp.join( output_dir, 'head_camera--slash--rgb--slash--throttled' - '--slash--image_rect_color--slash--compressed-with-audio.mp4') + '--slash--image_rect_color--slash--compressed.mp4') output_filename = osp.join(path, 'tests', 'output', 'video_to_bag.bag') cmd = 'rosrun jsk_rosbag_tools video_to_bag.py {} ' \ '--out {} --no-progress-bar'.format( From fecd5ffee62ebf50a1a4861d91cb562d85946537 Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 13:26:59 +0000 Subject: [PATCH 17/45] [jsk_rosbag_tools] chmod u+x test_jsk_rosbag_tools.py --- jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py old mode 100644 new mode 100755 From 720818e1faff2ff0c5cb02f2a3b302ccfa4c897d Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 13:27:51 +0000 Subject: [PATCH 18/45] [jsk_rosbag_tools] Add shebang --- jsk_rosbag_tools/scripts/merge.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jsk_rosbag_tools/scripts/merge.py b/jsk_rosbag_tools/scripts/merge.py index 955db22b8..21e0851de 100755 --- a/jsk_rosbag_tools/scripts/merge.py +++ b/jsk_rosbag_tools/scripts/merge.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import argparse from jsk_rosbag_tools.merge import merge_bag From d0f0e1218520350e61d645eb8685868e2805c5bb Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 13:28:21 +0000 Subject: [PATCH 19/45] [jsk_rosbag_tools] Enable python2 compatibility --- .../python/jsk_rosbag_tools/video.py | 140 +++++++++--------- jsk_rosbag_tools/scripts/video_to_bag.py | 20 ++- 2 files changed, 85 insertions(+), 75 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py index c039aa896..8066db4e6 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py @@ -1,5 +1,6 @@ from __future__ import division +import time import datetime import math import os.path as osp @@ -22,6 +23,10 @@ from jsk_rosbag_tools.merge import merge_bag +def to_seconds(date): + return time.mktime(date.timetuple()) + + def mediainfo(filepath): prober = 'ffprobe' command_args = [ @@ -123,77 +128,78 @@ def video_to_bag(video_filepath, bag_output_filepath, base_unixtime=None, show_progress_bar=True): if base_unixtime is None: - base_unixtime = datetime.datetime.now().timestamp() + base_unixtime = to_seconds(datetime.datetime.now()) topic_name = topic_name.lstrip('/compressed') if compress is True: topic_name = osp.join(topic_name, 'compressed') - with tempfile.TemporaryDirectory() as tmpdirname: - video_out = osp.join(tmpdirname, 'video.tmp.bag') - n_frame = get_video_n_frame(video_filepath) - if show_progress_bar: - progress = tqdm(total=n_frame) - with rosbag.Bag(video_out, 'w') as outbag: - for img, timestamp in load_frame(video_filepath): - if show_progress_bar: - progress.update(1) - msg = img_to_msg(img, compress=compress) - sec = int(base_unixtime + timestamp) - nsec = ((base_unixtime + timestamp) * (10 ** 9)) % (10 ** 9) - ros_timestamp = rospy.rostime.Time(sec, nsec) - msg.header.stamp = ros_timestamp - outbag.write(topic_name, msg, ros_timestamp) - - extract_audio = True - if no_audio is False: - wav_filepath = osp.join(tmpdirname, 'tmp.wav') - cmd = "ffmpeg -i '{}' '{}'".format( - video_filepath, wav_filepath) - subprocess.run(cmd, shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - try: - wf = wave.open(wav_filepath, mode='rb') - sample_rate = wf.getframerate() - wf.rewind() - buf = wf.readframes(-1) - if wf.getsampwidth() == 2: - data = np.frombuffer(buf, dtype='int16') - elif wf.getsampwidth() == 4: - data = np.frombuffer(buf, dtype='int32') - data = data.reshape(-1, wf.getnchannels()) - media_info = mediainfo(wav_filepath) - except RuntimeError: - extract_audio = False - - if extract_audio: - rate = 100 - n = int(np.ceil(data.shape[0] / (sample_rate // rate))) - channels = data.shape[1] - - audio_out = osp.join(tmpdirname, 'audio.tmp.bag') - with rosbag.Bag(audio_out, 'w') as outbag: - audio_info = audio_common_msgs.msg.AudioInfo( - channels=channels, - sample_rate=sample_rate, - sample_format=media_info['codec_name'].upper(), - bitrate=int(media_info['bit_rate']), - coding_format='wave') - outbag.write(audio_topic_name + '_info', - audio_info, ros_timestamp) - for i, audio_data in enumerate(nsplit(data, n)): - msg = audio_common_msgs.msg.AudioData() - msg.data = audio_data.reshape(-1).tobytes() - timestamp = i * 0.01 - sec = int(base_unixtime + timestamp) - nsec = ((base_unixtime + timestamp) - * (10 ** 9)) % (10 ** 9) - ros_timestamp = rospy.rostime.Time(sec, nsec) - outbag.write(audio_topic_name, msg, ros_timestamp) - merge_bag(video_out, audio_out, bag_output_filepath) - else: - shutil.move(video_out, bag_output_filepath) + tmpdirname = tempfile.mkdtemp("", 'tmp', None) + video_out = osp.join(tmpdirname, 'video.tmp.bag') + n_frame = get_video_n_frame(video_filepath) + if show_progress_bar: + progress = tqdm(total=n_frame) + with rosbag.Bag(video_out, 'w') as outbag: + for img, timestamp in load_frame(video_filepath): + if show_progress_bar: + progress.update(1) + msg = img_to_msg(img, compress=compress) + sec = int(base_unixtime + timestamp) + nsec = ((base_unixtime + timestamp) * (10 ** 9)) % (10 ** 9) + ros_timestamp = rospy.rostime.Time(sec, nsec) + msg.header.stamp = ros_timestamp + outbag.write(topic_name, msg, ros_timestamp) + + extract_audio = True + if no_audio is False: + wav_filepath = osp.join(tmpdirname, 'tmp.wav') + cmd = "ffmpeg -i '{}' '{}'".format( + video_filepath, wav_filepath) + proc = subprocess.Popen(cmd, shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.wait() + + try: + wf = wave.open(wav_filepath, mode='rb') + sample_rate = wf.getframerate() + wf.rewind() + buf = wf.readframes(-1) + if wf.getsampwidth() == 2: + data = np.frombuffer(buf, dtype='int16') + elif wf.getsampwidth() == 4: + data = np.frombuffer(buf, dtype='int32') + data = data.reshape(-1, wf.getnchannels()) + media_info = mediainfo(wav_filepath) + except RuntimeError: + extract_audio = False + + if extract_audio: + rate = 100 + n = int(np.ceil(data.shape[0] / (sample_rate // rate))) + channels = data.shape[1] + + audio_out = osp.join(tmpdirname, 'audio.tmp.bag') + with rosbag.Bag(audio_out, 'w') as outbag: + audio_info = audio_common_msgs.msg.AudioInfo( + channels=channels, + sample_rate=sample_rate, + sample_format=media_info['codec_name'].upper(), + bitrate=int(media_info['bit_rate']), + coding_format='wave') + outbag.write(audio_topic_name + '_info', + audio_info, ros_timestamp) + for i, audio_data in enumerate(nsplit(data, n)): + msg = audio_common_msgs.msg.AudioData() + msg.data = audio_data.reshape(-1).tobytes() + timestamp = i * 0.01 + sec = int(base_unixtime + timestamp) + nsec = ((base_unixtime + timestamp) + * (10 ** 9)) % (10 ** 9) + ros_timestamp = rospy.rostime.Time(sec, nsec) + outbag.write(audio_topic_name, msg, ros_timestamp) + merge_bag(video_out, audio_out, bag_output_filepath) else: shutil.move(video_out, bag_output_filepath) + else: + shutil.move(video_out, bag_output_filepath) diff --git a/jsk_rosbag_tools/scripts/video_to_bag.py b/jsk_rosbag_tools/scripts/video_to_bag.py index 3fa660cd5..87f77f2b8 100755 --- a/jsk_rosbag_tools/scripts/video_to_bag.py +++ b/jsk_rosbag_tools/scripts/video_to_bag.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import argparse -from pathlib import Path +import os.path as osp from jsk_rosbag_tools.video import video_to_bag @@ -22,15 +22,19 @@ def main(): help="Don't show progress bar.") args = parser.parse_args() - video_path = Path(args.inputvideo) + video_path = args.inputvideo if args.out is None: - args.out = video_path.with_suffix('.bag') - - outfile = Path(args.out) - pattern = str(video_path.parent / (video_path.stem + "_%i.bag")) + args.out = osp.join( + osp.dirname(video_path), + osp.splitext(osp.basename(video_path))[0] + '.bag') + + outfile = args.out + pattern = str(osp.join( + osp.dirname(video_path), + osp.splitext(osp.basename(video_path))[0]+ "_%i.bag")) index = 0 - while outfile.exists(): - outfile = Path(pattern % index) + while osp.exists(outfile): + outfile = pattern % index index += 1 video_to_bag( video_path, outfile, From 4c69aab2506c423bd70408210367bd828cc4b01f Mon Sep 17 00:00:00 2001 From: iory Date: Wed, 1 Jun 2022 13:28:37 +0000 Subject: [PATCH 20/45] [jsk_rosbag_tools] Refactor dependencies --- jsk_rosbag_tools/package.xml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jsk_rosbag_tools/package.xml b/jsk_rosbag_tools/package.xml index 10d5c62e6..a53d64475 100644 --- a/jsk_rosbag_tools/package.xml +++ b/jsk_rosbag_tools/package.xml @@ -11,15 +11,18 @@ catkin python-catkin-pkg-modules - cv_bridge - sensor_msgs + audio_common_msgs + cv_bridge + ffmpeg python-moviepy-pip python-numpy python-rospkg-modules + python-scipy python-setuptools python-termcolor python-tqdm python-yaml + sensor_msgs roslint rostest From 28c85e451c06681bdfb72b389e61e66fa56952a1 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 2 Jun 2022 14:00:23 +0900 Subject: [PATCH 21/45] [jsk_rosbag_tools] Decompress as much as possible --- .../python/jsk_rosbag_tools/cv.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py index bcc2532d6..a420604fc 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py @@ -37,6 +37,29 @@ def compressed_format(msg): def decompresse_imgmsg(msg): + if ';' not in msg.format: + fmt = '' + if msg.format not in ['png', 'jpeg', 'rvl']: + raise RuntimeError( + 'Unsupported or invalid compresssion format {}' + 'Please report this error' + 'https://github.com/jsk-ros-pkg/jsk_common/issues/new' + .format(msg.format)) + if msg.format in ['rvl']: + compr_type = 'compressedDepth' + else: + try: + return msg_to_bgr(msg, compressed=True) + except Exception: + pass + try: + return msg_to_np_depth(msg, compressed=True) + except Exception: + raise RuntimeError( + 'Unsupported or invalid compresssion format {}' + 'Please report this error' + 'https://github.com/jsk-ros-pkg/jsk_common/issues/new' + .format(msg.format)) fmt, compr_type = compressed_format(msg) if compr_type == 'compressedDepth': return msg_to_np_depth(msg, compressed=True) From d7969e57057a9257775ac495fbc2f68942135464 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 2 Jun 2022 14:12:36 +0900 Subject: [PATCH 22/45] [jsk_rosbag_tools] Fixed E225 missing whitespace around operator --- jsk_rosbag_tools/scripts/video_to_bag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/scripts/video_to_bag.py b/jsk_rosbag_tools/scripts/video_to_bag.py index 87f77f2b8..6d8f42eed 100755 --- a/jsk_rosbag_tools/scripts/video_to_bag.py +++ b/jsk_rosbag_tools/scripts/video_to_bag.py @@ -31,7 +31,7 @@ def main(): outfile = args.out pattern = str(osp.join( osp.dirname(video_path), - osp.splitext(osp.basename(video_path))[0]+ "_%i.bag")) + osp.splitext(osp.basename(video_path))[0] + "_%i.bag")) index = 0 while osp.exists(outfile): outfile = pattern % index From 6fdbfcf5d554e9399df8c8bf98a1b687596e5219 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 2 Jun 2022 14:52:56 +0900 Subject: [PATCH 23/45] [jsk_rosbag_tools] Rename extract_audio to bag_to_audio --- .../python/jsk_rosbag_tools/bag_to_audio.py | 50 +++++++++++++++++++ .../python/jsk_rosbag_tools/bag_to_video.py | 12 ++--- .../python/jsk_rosbag_tools/extract.py | 45 ----------------- .../python/jsk_rosbag_tools/video.py | 2 +- jsk_rosbag_tools/scripts/bag_to_audio.py | 4 +- 5 files changed, 59 insertions(+), 54 deletions(-) create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_audio.py diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_audio.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_audio.py new file mode 100644 index 000000000..acdab61f8 --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_audio.py @@ -0,0 +1,50 @@ +import os +import os.path as osp + +import numpy as np +import rosbag +from scipy.io.wavfile import write as wav_write + +from jsk_rosbag_tools.extract import extract_oneshot_topic +from jsk_rosbag_tools.info import get_topic_dict +from jsk_rosbag_tools.makedirs import makedirs + + +def bag_to_audio(bag_filepath, + wav_outpath, + topic_name='/audio', + audio_info_topic_name=None, + samplerate=44100, + channels=1, + overwrite=True): + if os.path.exists(wav_outpath) and overwrite is False: + raise FileExistsError('{} file already exists.'.format(wav_outpath)) + topic_dict = get_topic_dict(bag_filepath) + if topic_name not in topic_dict: + return + + audio_info_topic_name = audio_info_topic_name or topic_name + '_info' + # if audio_info_topic exists, extract info from it. + if audio_info_topic_name in topic_dict: + audio_info = extract_oneshot_topic(bag_filepath, audio_info_topic_name) + if audio_info is not None: + samplerate = audio_info.sample_rate + channels = audio_info.channels + + bag = rosbag.Bag(bag_filepath) + audio_buffer = [] + for _, msg, _ in bag.read_messages(topics=[topic_name]): + if msg._type == 'audio_common_msgs/AudioData': + buf = np.frombuffer(msg.data, dtype='int16') + buf = buf.reshape(-1, channels) + audio_buffer.append(buf) + audio_buffer = np.concatenate(audio_buffer, axis=0) + + makedirs(osp.dirname(wav_outpath)) + wav_write(wav_outpath, rate=samplerate, data=audio_buffer) + + valid = os.stat(wav_outpath).st_size != 0 + if valid is False: + return False + + return True diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index af27adea4..e5e7f4b08 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -6,10 +6,10 @@ import cv2 from moviepy.audio.io.AudioFileClip import AudioFileClip -from moviepy.video.io.VideoFileClip import VideoFileClip from moviepy.video.io.ffmpeg_writer import FFMPEG_VideoWriter +from moviepy.video.io.VideoFileClip import VideoFileClip -from jsk_rosbag_tools.extract import extract_audio +from jsk_rosbag_tools.bag_to_audio import bag_to_audio from jsk_rosbag_tools.extract import extract_image_topic from jsk_rosbag_tools.extract import get_image_topic_names from jsk_rosbag_tools.makedirs import makedirs @@ -65,10 +65,10 @@ def bag_to_video(input_bagfile, wav_outpath = osp.join(output_dirpath, '{}.wav'.format( topic_name_to_file_name(audio_topic))) - audio_exists = extract_audio(input_bagfile, wav_outpath, - samplerate=samplerate, - channels=channels, - topic_name=audio_topic) + audio_exists = bag_to_audio(input_bagfile, wav_outpath, + samplerate=samplerate, + channels=channels, + topic_name=audio_topic) dt = 1.0 / fps for image_topic, output_filepath in zip(target_image_topics, diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py index e99066332..fcb8497f8 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py @@ -1,15 +1,10 @@ -import os -import os.path as osp - import numpy as np import rosbag -from scipy.io.wavfile import write as wav_write from jsk_rosbag_tools.cv import compressed_format from jsk_rosbag_tools.cv import decompresse_imgmsg from jsk_rosbag_tools.cv import msg_to_img from jsk_rosbag_tools.info import get_topic_dict -from jsk_rosbag_tools.makedirs import makedirs def get_image_topic_names(bag_filepath, @@ -93,43 +88,3 @@ def extract_image_topic(bag_filepath, topic_name): bgr_img = np.concatenate([bgr_img, pad_img], axis=1) yield msg.header.stamp.to_sec(), topic, bgr_img, encoding - - -def extract_audio(bag_filepath, - wav_outpath, - topic_name='/audio', - audio_info_topic_name=None, - samplerate=44100, - channels=1, - overwrite=True): - if os.path.exists(wav_outpath) and overwrite is False: - raise FileExistsError('{} file already exists.'.format(wav_outpath)) - topic_dict = get_topic_dict(bag_filepath) - if topic_name not in topic_dict: - return - - audio_info_topic_name = audio_info_topic_name or topic_name + '_info' - # if audio_info_topic exists, extract info from it. - if audio_info_topic_name in topic_dict: - audio_info = extract_oneshot_topic(bag_filepath, audio_info_topic_name) - if audio_info is not None: - samplerate = audio_info.sample_rate - channels = audio_info.channels - - bag = rosbag.Bag(bag_filepath) - audio_buffer = [] - for _, msg, _ in bag.read_messages(topics=[topic_name]): - if msg._type == 'audio_common_msgs/AudioData': - buf = np.frombuffer(msg.data, dtype='int16') - buf = buf.reshape(-1, channels) - audio_buffer.append(buf) - audio_buffer = np.concatenate(audio_buffer, axis=0) - - makedirs(osp.dirname(wav_outpath)) - wav_write(wav_outpath, rate=samplerate, data=audio_buffer) - - valid = os.stat(wav_outpath).st_size != 0 - if valid is False: - return False - - return True diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py index 8066db4e6..996033ef4 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py @@ -1,6 +1,5 @@ from __future__ import division -import time import datetime import math import os.path as osp @@ -9,6 +8,7 @@ import subprocess import sys import tempfile +import time import wave import audio_common_msgs.msg diff --git a/jsk_rosbag_tools/scripts/bag_to_audio.py b/jsk_rosbag_tools/scripts/bag_to_audio.py index c11141365..7b73eb5fb 100755 --- a/jsk_rosbag_tools/scripts/bag_to_audio.py +++ b/jsk_rosbag_tools/scripts/bag_to_audio.py @@ -5,7 +5,7 @@ import termcolor -from jsk_rosbag_tools.extract import extract_audio +from jsk_rosbag_tools.bag_to_audio import bag_to_audio def main(): @@ -27,7 +27,7 @@ def main(): osp.dirname(args.input_bagfile), osp.splitext(osp.basename(args.input_bagfile))[0]) - audio_exists = extract_audio( + audio_exists = bag_to_audio( args.input_bagfile, args.out, samplerate=args.samplerate, channels=args.channels, From 186417dee91c12818a45758604561fb385eb0a78 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 2 Jun 2022 15:12:21 +0900 Subject: [PATCH 24/45] [jsk_rosbag_tools] Fixed W503 line break before binary operator --- jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py | 4 ++-- jsk_rosbag_tools/python/jsk_rosbag_tools/video.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py index a420604fc..bfb941244 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py @@ -134,8 +134,8 @@ def msg_to_np_depth(msg, compressed=False, rescale=True): # it to 16UC1 again (depth in mm) depth_img = depth_img_scaled else: - raise Exception("Decoding of '" + depth_fmt - + "' is not implemented!") + raise Exception( + "Decoding of '" + depth_fmt + "' is not implemented!") else: depth_img = _bridge.imgmsg_to_cv2( msg, diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py index 996033ef4..97aab8112 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py @@ -194,8 +194,8 @@ def video_to_bag(video_filepath, bag_output_filepath, msg.data = audio_data.reshape(-1).tobytes() timestamp = i * 0.01 sec = int(base_unixtime + timestamp) - nsec = ((base_unixtime + timestamp) - * (10 ** 9)) % (10 ** 9) + nsec = ( + (base_unixtime + timestamp) * (10 ** 9)) % (10 ** 9) ros_timestamp = rospy.rostime.Time(sec, nsec) outbag.write(audio_topic_name, msg, ros_timestamp) merge_bag(video_out, audio_out, bag_output_filepath) From 46df7250d9b40cb68d70fc1495b5c693c1f1c116 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 2 Jun 2022 15:33:27 +0900 Subject: [PATCH 25/45] [jsk_rosbag_tools] Check topic exists --- .../python/jsk_rosbag_tools/bag_to_video.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index e5e7f4b08..d0a3c32db 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -43,6 +43,7 @@ def bag_to_video(input_bagfile, output_filepaths = [] target_image_topics = [] + candidate_topics = get_image_topic_names(input_bagfile, rgb_only=True) if output_filepath is not None: if image_topic is None: raise ValueError( @@ -53,8 +54,7 @@ def bag_to_video(input_bagfile, else: # output_dirpath is specified case. if image_topics is None: - image_topics = get_image_topic_names( - input_bagfile, rgb_only=True) + image_topics = candidate_topics target_image_topics = image_topics for image_topic in target_image_topics: @@ -65,6 +65,14 @@ def bag_to_video(input_bagfile, wav_outpath = osp.join(output_dirpath, '{}.wav'.format( topic_name_to_file_name(audio_topic))) + # check topics exist. + not_exists_topics = list(filter( + lambda tn: tn not in candidate_topics, target_image_topics)) + if len(not_exists_topics) > 0: + raise ValueError( + 'Topics that are not included in the rosbag are specified.' + ' {}'.format(list(not_exists_topics))) + audio_exists = bag_to_audio(input_bagfile, wav_outpath, samplerate=samplerate, channels=channels, From 117d51197f3b977605edfae91b943de7200b4905 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 2 Jun 2022 16:38:50 +0900 Subject: [PATCH 26/45] [jsk_rosbag_tools] Fixed compr_type condition --- jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py index bfb941244..5e8c4a8e0 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py @@ -60,7 +60,8 @@ def decompresse_imgmsg(msg): 'Please report this error' 'https://github.com/jsk-ros-pkg/jsk_common/issues/new' .format(msg.format)) - fmt, compr_type = compressed_format(msg) + else: + fmt, compr_type = compressed_format(msg) if compr_type == 'compressedDepth': return msg_to_np_depth(msg, compressed=True) else: From b1a39185e4e48966b4641986a547569930af1f40 Mon Sep 17 00:00:00 2001 From: Guilherme Affonso Date: Thu, 2 Jun 2022 20:31:09 +0900 Subject: [PATCH 27/45] [jsk_rosbag_tools] Allow to record mono8 topics for mask images --- .../python/jsk_rosbag_tools/bag_to_video.py | 7 +++++-- jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index d0a3c32db..b8e91110f 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -43,7 +43,8 @@ def bag_to_video(input_bagfile, output_filepaths = [] target_image_topics = [] - candidate_topics = get_image_topic_names(input_bagfile, rgb_only=True) + candidate_topics = get_image_topic_names(input_bagfile) + if output_filepath is not None: if image_topic is None: raise ValueError( @@ -54,7 +55,9 @@ def bag_to_video(input_bagfile, else: # output_dirpath is specified case. if image_topics is None: - image_topics = candidate_topics + # record all color topics + image_topics = get_image_topic_names(input_bagfile, + rgb_only=True) target_image_topics = image_topics for image_topic in target_image_topics: diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py index 5e8c4a8e0..398172093 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py @@ -24,6 +24,8 @@ def msg_to_img(msg): if msg.encoding in ['bgra8', 'bgr8', 'rgba8', 'rgb8']: return msg_to_bgr(msg) + elif msg.encoding in ['mono8']: + return msg_to_mono(msg) else: return msg_to_np_depth(msg) @@ -89,6 +91,18 @@ def msg_to_bgr(msg, compressed=False): return bgr_img +def msg_to_mono(msg, compressed=False): + if compressed: + np_arr = np.fromstring(msg.data, np.uint8) + img = cv2.imdecode( + np_arr, cv2.IMREAD_GRAYSCALE) + else: + img = _bridge.imgmsg_to_cv2( + msg, + desired_encoding=msg.encoding) + return img + + def msg_to_np_depth(msg, compressed=False, rescale=True): if compressed: # 'msg' as type CompressedImage From 3b03d0ecc6e801c0e728a19ac07eff455d1799a2 Mon Sep 17 00:00:00 2001 From: Guilherme Affonso Date: Thu, 2 Jun 2022 20:51:25 +0900 Subject: [PATCH 28/45] [jsk_rosbag_tools] Fix bug when saving to local path with --image-topic --- jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index b8e91110f..f77db0e6a 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -84,7 +84,9 @@ def bag_to_video(input_bagfile, dt = 1.0 / fps for image_topic, output_filepath in zip(target_image_topics, output_filepaths): - makedirs(osp.dirname(output_filepath)) + filepath_dir = osp.dirname(output_filepath) + if filepath_dir: + makedirs(filepath_dir) if audio_exists: tmp_videopath = tempfile.NamedTemporaryFile(suffix='.mp4').name else: From d3f7727b0e1c315981c411217261fdffd2c98a21 Mon Sep 17 00:00:00 2001 From: iory Date: Sat, 4 Jun 2022 14:32:34 +0900 Subject: [PATCH 29/45] [jsk_rosbag_tools] Resize every frame --- .../python/jsk_rosbag_tools/bag_to_video.py | 13 +++-- .../python/jsk_rosbag_tools/image_utils.py | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 jsk_rosbag_tools/python/jsk_rosbag_tools/image_utils.py diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index f77db0e6a..b175d3063 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -12,6 +12,7 @@ from jsk_rosbag_tools.bag_to_audio import bag_to_audio from jsk_rosbag_tools.extract import extract_image_topic from jsk_rosbag_tools.extract import get_image_topic_names +from jsk_rosbag_tools.image_utils import resize_keeping_aspect_ratio from jsk_rosbag_tools.makedirs import makedirs from jsk_rosbag_tools.topic_name_utils import topic_name_to_file_name @@ -99,12 +100,13 @@ def bag_to_video(input_bagfile, while stamp == 0.0: stamp, _, img, _ = next(images) start_stamp = stamp + width, height = img.shape[1], img.shape[0] creation_time = datetime.datetime.utcfromtimestamp(start_stamp) time_format = '%y-%m-%d %h:%M:%S' writer = FFMPEG_VideoWriter( tmp_videopath, - (img.shape[1], img.shape[0]), + (width, height), fps, logfile=None, ffmpeg_params=[ '-metadata', @@ -113,14 +115,15 @@ def bag_to_video(input_bagfile, ]) current_time = 0.0 - cur_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - for stamp, _, bgr_img, _ in images: - rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) + cur_img = resize_keeping_aspect_ratio( + cv2.cvtColor(img, cv2.COLOR_BGR2RGB), width=width) + for i, (stamp, _, bgr_img, _) in enumerate(images): aligned_stamp = stamp - start_stamp while current_time < aligned_stamp: current_time += dt writer.write_frame(cur_img) - cur_img = rgb_img + cur_img = resize_keeping_aspect_ratio( + cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB), width=width) writer.write_frame(cur_img) current_time += dt writer.close() diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/image_utils.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/image_utils.py new file mode 100644 index 000000000..0220b4a8b --- /dev/null +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/image_utils.py @@ -0,0 +1,53 @@ +import cv2 +import PIL + + +def pil_to_cv2_interpolation(interpolation): + if isinstance(interpolation, str): + interpolation = interpolation.lower() + if interpolation == 'nearest': + cv_interpolation = cv2.INTER_NEAREST + elif interpolation == 'bilinear': + cv_interpolation = cv2.INTER_LINEAR + elif interpolation == 'bicubic': + cv_interpolation = cv2.INTER_CUBIC + elif interpolation == 'lanczos': + cv_interpolation = cv2.INTER_LANCZOS4 + else: + raise ValueError( + 'Not valid Interpolation. ' + 'Valid interpolation methods are ' + 'nearest, bilinear, bicubic and lanczos.') + else: + if interpolation == PIL.Image.NEAREST: + cv_interpolation = cv2.INTER_NEAREST + elif interpolation == PIL.Image.BILINEAR: + cv_interpolation = cv2.INTER_LINEAR + elif interpolation == PIL.Image.BICUBIC: + cv_interpolation = cv2.INTER_CUBIC + elif interpolation == PIL.Image.LANCZOS: + cv_interpolation = cv2.INTER_LANCZOS4 + else: + raise ValueError( + 'Not valid Interpolation. ' + 'Valid interpolation methods are ' + 'PIL.Image.NEAREST, PIL.Image.BILINEAR, ' + 'PIL.Image.BICUBIC and PIL.Image.LANCZOS.') + return cv_interpolation + + +def resize_keeping_aspect_ratio(img, width=None, height=None, + interpolation='bilinear'): + if (width and height) or (width is None and height is None): + raise ValueError('Only width or height should be specified.') + if width == img.shape[1] and height == img.shape[0]: + return img + if width: + height = width * img.shape[0] / img.shape[1] + else: + width = height * img.shape[1] / img.shape[0] + height = int(height) + width = int(width) + cv_interpolation = pil_to_cv2_interpolation(interpolation) + return cv2.resize(img, (width, height), + interpolation=cv_interpolation) From f3ec40d976e645b78f2ba6ae429d777e1289cef4 Mon Sep 17 00:00:00 2001 From: iory Date: Sat, 4 Jun 2022 15:30:09 +0900 Subject: [PATCH 30/45] [jsk_rosbag_tools] Add message to raise --- jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py | 4 +++- .../python/jsk_rosbag_tools/extract.py | 15 +++++++++------ .../python/jsk_rosbag_tools/makedirs.py | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py index 398172093..e8000897c 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py @@ -211,6 +211,8 @@ def compress_depth_msg(msg, depth_quantization=100, depth_max=None): compressed_msg.data += np.array( cv2.imencode('.png', depth)[1]).tostring() else: - raise NotImplementedError + raise NotImplementedError( + 'Unsupported compressed depth image format {}' + .format(msg.encoding)) return compressed_msg diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py index fcb8497f8..087a9541f 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/extract.py @@ -52,15 +52,16 @@ def extract_image_topic(bag_filepath, topic_name): with rosbag.Bag(bag_filepath, 'r') as input_rosbag: for topic, msg, _ in input_rosbag.read_messages( topics=[topic_name]): - if topic_dict[topic]['type'] == 'sensor_msgs/Image': + topic_type = topic_dict[topic]['type'] + if topic_type == 'sensor_msgs/Image': bgr_img = msg_to_img(msg) encoding = msg.encoding - elif topic_dict[topic]['type'] == \ - 'sensor_msgs/CompressedImage': + elif topic_type == 'sensor_msgs/CompressedImage': bgr_img = decompresse_imgmsg(msg) encoding, _ = compressed_format(msg) else: - raise RuntimeError + raise RuntimeError('Unsupported Image topic {}'.format( + topic_type)) # padding image if bgr_img.shape[0] % 2 != 0: @@ -72,7 +73,8 @@ def extract_image_topic(bag_filepath, topic_name): (1, bgr_img.shape[1], bgr_img.shape[2]), dtype=bgr_img.dtype) else: - raise ValueError + raise ValueError('Invalid image shape {}' + .format(bgr_img.shape)) bgr_img = np.concatenate([bgr_img, pad_img], axis=0) if bgr_img.shape[1] % 2 != 0: @@ -84,7 +86,8 @@ def extract_image_topic(bag_filepath, topic_name): (bgr_img.shape[0], 1, bgr_img.shape[2]), dtype=bgr_img.dtype) else: - raise ValueError + raise ValueError('Invalid image shape {}' + .format(bgr_img.shape)) bgr_img = np.concatenate([bgr_img, pad_img], axis=1) yield msg.header.stamp.to_sec(), topic, bgr_img, encoding diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/makedirs.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/makedirs.py index 0edf55ab7..cc3795eae 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/makedirs.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/makedirs.py @@ -27,6 +27,6 @@ def makedirs(name, mode=0o777, exist_ok=True): os.makedirs(name, mode) except OSError: if not (exist_ok and os.path.isdir(name)): - raise OSError + raise OSError('Directory {} already exists'.format(name)) else: os.makedirs(name, mode, exist_ok=exist_ok) From 34986ebee928c3699a8a4b12c1aaafed1c52799c Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 9 Jun 2022 12:36:44 +0900 Subject: [PATCH 31/45] [jsk_rosbag_tools] Add fps option --- .../python/jsk_rosbag_tools/video.py | 44 +++++++++++++++++-- jsk_rosbag_tools/scripts/video_to_bag.py | 3 ++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py index 97aab8112..e2b4f24de 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py @@ -84,7 +84,12 @@ def get_video_duration(video_path): video_path = str(video_path) if not osp.exists(video_path): raise OSError("{} not exists".format(video_path)) - return VideoFileClip(video_path).duration + cap = cv2.VideoCapture(video_path) + fps = cap.get(cv2.CAP_PROP_FPS) + frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + cap.release() + duration = frame_count/fps + return duration def get_video_n_frame(video_path): @@ -95,6 +100,14 @@ def get_video_n_frame(video_path): return int(clip.duration * clip.fps) +def get_video_fps(video_path): + video_path = str(video_path) + if not osp.exists(video_path): + raise OSError("{} not exists".format(video_path)) + clip = VideoFileClip(video_path) + return clip.fps + + def load_frame(video_path, start=0.0, duration=-1, target_size=None, sampling_frequency=None): video_path = str(video_path) @@ -122,11 +135,31 @@ def load_frame(video_path, start=0.0, duration=-1, vid.release() +def count_frames(video_path, start=0.0, duration=-1, + sampling_frequency=None): + video_duration = get_video_duration(video_path) + video_duration -= start + if duration > 0: + video_duration = max(video_duration - duration, 0) + fps = get_video_fps(video_path) + if sampling_frequency is not None: + return int(math.ceil( + video_duration * fps + / int(math.ceil(fps * sampling_frequency)))) + else: + return int(math.ceil(video_duration * fps)) + + def video_to_bag(video_filepath, bag_output_filepath, topic_name, compress=False, audio_topic_name='/audio', no_audio=False, base_unixtime=None, + fps=None, show_progress_bar=True): + if fps is not None: + sampling_frequency = 1.0 / fps + else: + sampling_frequency = None if base_unixtime is None: base_unixtime = to_seconds(datetime.datetime.now()) @@ -136,11 +169,14 @@ def video_to_bag(video_filepath, bag_output_filepath, tmpdirname = tempfile.mkdtemp("", 'tmp', None) video_out = osp.join(tmpdirname, 'video.tmp.bag') - n_frame = get_video_n_frame(video_filepath) + n_frame = count_frames(video_filepath, + sampling_frequency=sampling_frequency) if show_progress_bar: progress = tqdm(total=n_frame) with rosbag.Bag(video_out, 'w') as outbag: - for img, timestamp in load_frame(video_filepath): + for img, timestamp in load_frame( + video_filepath, + sampling_frequency=sampling_frequency): if show_progress_bar: progress.update(1) msg = img_to_msg(img, compress=compress) @@ -149,6 +185,8 @@ def video_to_bag(video_filepath, bag_output_filepath, ros_timestamp = rospy.rostime.Time(sec, nsec) msg.header.stamp = ros_timestamp outbag.write(topic_name, msg, ros_timestamp) + if show_progress_bar: + progress.close() extract_audio = True if no_audio is False: diff --git a/jsk_rosbag_tools/scripts/video_to_bag.py b/jsk_rosbag_tools/scripts/video_to_bag.py index 6d8f42eed..8accea3c4 100755 --- a/jsk_rosbag_tools/scripts/video_to_bag.py +++ b/jsk_rosbag_tools/scripts/video_to_bag.py @@ -16,6 +16,8 @@ def main(): parser.add_argument('--topic-name', type=str, default='/video/rgb/image_raw', help='Converted topic name.') + parser.add_argument('--fps', type=int, + help='Frame Rate.', default=None) parser.add_argument('--compress', action='store_true', help='Compress Image flag.') parser.add_argument('--no-progress-bar', action='store_true', @@ -40,6 +42,7 @@ def main(): video_path, outfile, args.topic_name, compress=args.compress, + fps=args.fps, show_progress_bar=not args.no_progress_bar) From 84fe1196f3253552db010ea5ae3a3e1e256e4706 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 9 Jun 2022 12:37:00 +0900 Subject: [PATCH 32/45] [jsk_rosbag_tools] Add .wav ext --- jsk_rosbag_tools/scripts/bag_to_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/scripts/bag_to_audio.py b/jsk_rosbag_tools/scripts/bag_to_audio.py index 7b73eb5fb..ac3ad9fd8 100755 --- a/jsk_rosbag_tools/scripts/bag_to_audio.py +++ b/jsk_rosbag_tools/scripts/bag_to_audio.py @@ -25,7 +25,7 @@ def main(): if len(args.out) == 0: args.out = osp.join( osp.dirname(args.input_bagfile), - osp.splitext(osp.basename(args.input_bagfile))[0]) + osp.splitext(osp.basename(args.input_bagfile))[0] + '.wav') audio_exists = bag_to_audio( args.input_bagfile, args.out, From 12fcea8b4a3a6004ac633339ff905e4365a8aef6 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 9 Jun 2022 18:20:23 +0900 Subject: [PATCH 33/45] [jsk_rosbag_tools] Add show_progress_bar option --- .../python/jsk_rosbag_tools/bag_to_video.py | 30 +++++++++++++++++-- jsk_rosbag_tools/scripts/bag_to_video.py | 5 +++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index b175d3063..39b5b1207 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -8,11 +8,13 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip from moviepy.video.io.ffmpeg_writer import FFMPEG_VideoWriter from moviepy.video.io.VideoFileClip import VideoFileClip +from tqdm import tqdm from jsk_rosbag_tools.bag_to_audio import bag_to_audio from jsk_rosbag_tools.extract import extract_image_topic from jsk_rosbag_tools.extract import get_image_topic_names from jsk_rosbag_tools.image_utils import resize_keeping_aspect_ratio +from jsk_rosbag_tools.info import get_topic_dict from jsk_rosbag_tools.makedirs import makedirs from jsk_rosbag_tools.topic_name_utils import topic_name_to_file_name @@ -25,7 +27,8 @@ def bag_to_video(input_bagfile, fps=30, samplerate=16000, channels=1, - audio_topic='/audio'): + audio_topic='/audio', + show_progress_bar=True): """Create video from rosbag file. Specify only either output_filepath or output_dirpath. @@ -35,7 +38,8 @@ def bag_to_video(input_bagfile, """ if not os.path.exists(input_bagfile): - print('Input bagfile {} not exists.'.format(input_bagfile)) + print('[bag_to_video] Input bagfile {} not exists.' + .format(input_bagfile)) sys.exit(1) if output_filepath is not None and output_dirpath is not None: @@ -77,6 +81,7 @@ def bag_to_video(input_bagfile, 'Topics that are not included in the rosbag are specified.' ' {}'.format(list(not_exists_topics))) + print('[bag_to_video] Extracting audio from rosbag file.') audio_exists = bag_to_audio(input_bagfile, wav_outpath, samplerate=samplerate, channels=channels, @@ -85,6 +90,8 @@ def bag_to_video(input_bagfile, dt = 1.0 / fps for image_topic, output_filepath in zip(target_image_topics, output_filepaths): + print('[bag_to_video] Creating video of {} from rosbag file {}.' + .format(image_topic, input_bagfile)) filepath_dir = osp.dirname(output_filepath) if filepath_dir: makedirs(filepath_dir) @@ -94,11 +101,18 @@ def bag_to_video(input_bagfile, tmp_videopath = output_filepath images = extract_image_topic(input_bagfile, image_topic) + topic_info_dict = get_topic_dict(input_bagfile)[image_topic] + n_frame = topic_info_dict['messages'] + + if show_progress_bar: + progress = tqdm(total=n_frame) # remove 0 time stamp stamp = 0.0 while stamp == 0.0: stamp, _, img, _ = next(images) + if show_progress_bar: + progress.update(1) start_stamp = stamp width, height = img.shape[1], img.shape[0] @@ -118,6 +132,8 @@ def bag_to_video(input_bagfile, cur_img = resize_keeping_aspect_ratio( cv2.cvtColor(img, cv2.COLOR_BGR2RGB), width=width) for i, (stamp, _, bgr_img, _) in enumerate(images): + if show_progress_bar: + progress.update(1) aligned_stamp = stamp - start_stamp while current_time < aligned_stamp: current_time += dt @@ -128,8 +144,16 @@ def bag_to_video(input_bagfile, current_time += dt writer.close() + if show_progress_bar: + progress.close() + if audio_exists: + print('[bag_to_video] Combine video and audio') clip_output = VideoFileClip(tmp_videopath).subclip().\ set_audio(AudioFileClip(wav_outpath)) clip_output.write_videofile( - output_filepath) + output_filepath, + verbose=False, + logger='bar' if show_progress_bar else None) + print('[bag_to_video] Created video is saved to {}' + .format(output_filepath)) diff --git a/jsk_rosbag_tools/scripts/bag_to_video.py b/jsk_rosbag_tools/scripts/bag_to_video.py index 7782fe1c8..c37730197 100755 --- a/jsk_rosbag_tools/scripts/bag_to_video.py +++ b/jsk_rosbag_tools/scripts/bag_to_video.py @@ -22,6 +22,8 @@ def main(): parser.add_argument('--audio-topic', type=str, default='/audio') parser.add_argument('--image-topic', type=str, default=[], nargs='+', help='Topic name to extract.') + parser.add_argument('--no-progress-bar', action='store_true', + help="Don't show progress bar.") parser.add_argument('input_bagfile') args = parser.parse_args() @@ -50,7 +52,8 @@ def main(): fps=args.fps, samplerate=args.samplerate, channels=args.channels, - audio_topic=args.audio_topic) + audio_topic=args.audio_topic, + show_progress_bar=not args.no_progress_bar) if __name__ == '__main__': From 15c0a94e443707e3c4d4aa03f15159eb544656a3 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 9 Jun 2022 18:21:10 +0900 Subject: [PATCH 34/45] [jsk_rosbag_tools] Fixed E226 missing whitespace around arithmetic operator --- jsk_rosbag_tools/python/jsk_rosbag_tools/video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py index e2b4f24de..707a28334 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py @@ -88,7 +88,7 @@ def get_video_duration(video_path): fps = cap.get(cv2.CAP_PROP_FPS) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) cap.release() - duration = frame_count/fps + duration = frame_count / fps return duration From fd04e3152c4704796b26ffb32eff2b4affee6921 Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 9 Jun 2022 18:52:40 +0900 Subject: [PATCH 35/45] [jsk_rosbag_tools] Fixed W503 line break before binary operator --- jsk_rosbag_tools/python/jsk_rosbag_tools/video.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py index 707a28334..0e9dfdfc2 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/video.py @@ -144,8 +144,8 @@ def count_frames(video_path, start=0.0, duration=-1, fps = get_video_fps(video_path) if sampling_frequency is not None: return int(math.ceil( - video_duration * fps - / int(math.ceil(fps * sampling_frequency)))) + video_duration * fps / + int(math.ceil(fps * sampling_frequency)))) else: return int(math.ceil(video_duration * fps)) From f334654298b3f86394001507c5bf6d72c38853ed Mon Sep 17 00:00:00 2001 From: iory Date: Thu, 9 Jun 2022 23:19:42 +0900 Subject: [PATCH 36/45] [jsk_rosbag_tools] Add resize_keeping_aspect_ratio_wrt_target_size to fix video --- .../python/jsk_rosbag_tools/bag_to_video.py | 12 ++++++---- .../python/jsk_rosbag_tools/image_utils.py | 23 +++++++++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py index 39b5b1207..ce99028c7 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/bag_to_video.py @@ -13,7 +13,8 @@ from jsk_rosbag_tools.bag_to_audio import bag_to_audio from jsk_rosbag_tools.extract import extract_image_topic from jsk_rosbag_tools.extract import get_image_topic_names -from jsk_rosbag_tools.image_utils import resize_keeping_aspect_ratio +from jsk_rosbag_tools.image_utils import \ + resize_keeping_aspect_ratio_wrt_target_size from jsk_rosbag_tools.info import get_topic_dict from jsk_rosbag_tools.makedirs import makedirs from jsk_rosbag_tools.topic_name_utils import topic_name_to_file_name @@ -129,8 +130,8 @@ def bag_to_video(input_bagfile, ]) current_time = 0.0 - cur_img = resize_keeping_aspect_ratio( - cv2.cvtColor(img, cv2.COLOR_BGR2RGB), width=width) + cur_img = resize_keeping_aspect_ratio_wrt_target_size( + cv2.cvtColor(img, cv2.COLOR_BGR2RGB), width=width, height=height) for i, (stamp, _, bgr_img, _) in enumerate(images): if show_progress_bar: progress.update(1) @@ -138,8 +139,9 @@ def bag_to_video(input_bagfile, while current_time < aligned_stamp: current_time += dt writer.write_frame(cur_img) - cur_img = resize_keeping_aspect_ratio( - cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB), width=width) + cur_img = resize_keeping_aspect_ratio_wrt_target_size( + cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB), + width=width, height=height) writer.write_frame(cur_img) current_time += dt writer.close() diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/image_utils.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/image_utils.py index 0220b4a8b..1a8e98f9d 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/image_utils.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/image_utils.py @@ -1,4 +1,5 @@ import cv2 +import numpy as np import PIL @@ -43,11 +44,29 @@ def resize_keeping_aspect_ratio(img, width=None, height=None, if width == img.shape[1] and height == img.shape[0]: return img if width: - height = width * img.shape[0] / img.shape[1] + height = 1.0 * width * img.shape[0] / img.shape[1] else: - width = height * img.shape[1] / img.shape[0] + width = 1.0 * height * img.shape[1] / img.shape[0] height = int(height) width = int(width) cv_interpolation = pil_to_cv2_interpolation(interpolation) return cv2.resize(img, (width, height), interpolation=cv_interpolation) + + +def resize_keeping_aspect_ratio_wrt_target_size( + img, width, height, interpolation='bilinear', + background_color=(0, 0, 0)): + if width == img.shape[1] and height == img.shape[0]: + return img + H, W, _ = img.shape + ratio = min(float(height) / H, float(width) / W) + M = np.array([[ratio, 0, 0], + [0, ratio, 0]], dtype=np.float32) + dst = np.zeros((int(height), int(width), 3), dtype=img.dtype) + return cv2.warpAffine( + img, M, + (int(width), int(height)), + dst, + cv2.INTER_CUBIC, cv2.BORDER_CONSTANT, + background_color) From e881250ac6d2d13975f92619b2df39e6d909d167 Mon Sep 17 00:00:00 2001 From: iory Date: Fri, 10 Jun 2022 00:17:14 +0900 Subject: [PATCH 37/45] [jsk_rosbag_tools] Avoid raise ValueError on invalid compressed format. --- jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py index e8000897c..a408d0e88 100644 --- a/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py +++ b/jsk_rosbag_tools/python/jsk_rosbag_tools/cv.py @@ -31,7 +31,21 @@ def msg_to_img(msg): def compressed_format(msg): - fmt, compr_type = msg.format.split(';') + if ';' not in msg.format: + fmt = '' + if msg.format not in ['png', 'jpeg', 'rvl']: + raise RuntimeError( + 'Unsupported or invalid compresssion format {}' + 'Please report this error' + 'https://github.com/jsk-ros-pkg/jsk_common/issues/new' + .format(msg.format)) + if msg.format in ['rvl']: + compr_type = 'compressedDepth' + else: + # could not determine compressed format. + compr_type = '' + else: + fmt, compr_type = msg.format.split(';') # remove white space fmt = fmt.strip() compr_type = compr_type.strip() From ba11a331f515b84cf11a5d3ad1c37e2dda91ebc1 Mon Sep 17 00:00:00 2001 From: iory Date: Tue, 28 Jun 2022 01:41:37 +0900 Subject: [PATCH 38/45] [jsk_rosbag_tools] Refactor test to print command log --- .../tests/test_jsk_rosbag_tools.py | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py index 0fe05c23f..65183967f 100755 --- a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py +++ b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py @@ -5,6 +5,7 @@ import unittest import rospkg +import rospy PKG = 'jsk_rosbag_tools' @@ -13,6 +14,17 @@ class TestJSKRosBagTools(unittest.TestCase): + def _check_command(self, cmd): + proc = subprocess.Popen(cmd, shell=True, + stdout=subprocess.PIPE) + with proc.stdout: + for line in iter(proc.stdout.readline, b''): + rospy.loginfo('{}'.format(line.decode('utf-8').strip())) + returncode = proc.wait() + + if returncode != 0: + raise RuntimeError('command {} failed.'.format(cmd)) + def test_bag_to_audio(self): rospack = rospkg.RosPack() path = rospack.get_path('jsk_rosbag_tools') @@ -20,11 +32,7 @@ def test_bag_to_audio(self): '20220530173950_go_to_kitchen_rosbag.bag') cmd = 'rosrun jsk_rosbag_tools bag_to_audio.py {}'.format( video_bag_path) - proc = subprocess.Popen(cmd, shell=True) - proc.wait() - - if proc.returncode != 0: - raise RuntimeError + self._check_command(cmd) def test_tf_static_to_tf(self): rospack = rospkg.RosPack() @@ -33,11 +41,7 @@ def test_tf_static_to_tf(self): '20220530173950_go_to_kitchen_rosbag.bag') cmd = 'rosrun jsk_rosbag_tools tf_static_to_tf.py {} ' \ '--no-progress-bar'.format(video_bag_path) - proc = subprocess.Popen(cmd, shell=True) - proc.wait() - - if proc.returncode != 0: - raise RuntimeError + self._check_command(cmd) def test_merge(self): rospack = rospkg.RosPack() @@ -48,11 +52,7 @@ def test_merge(self): '2022-05-07-hello-test.bag') cmd = 'rosrun jsk_rosbag_tools merge.py {} {} -o {}'\ .format(audio_bag_path, audio_bag_path, output_path) - proc = subprocess.Popen(cmd, shell=True) - proc.wait() - - if proc.returncode != 0: - raise RuntimeError + self._check_command(cmd) def test_bag_to_video_and_video_to_bag_and_compress(self): rospack = rospkg.RosPack() @@ -64,22 +64,14 @@ def test_bag_to_video_and_video_to_bag_and_compress(self): cmd = 'rosrun jsk_rosbag_tools bag_to_video.py {} -o {}'.format( audio_bag_path, output_dir) - proc = subprocess.Popen(cmd, shell=True) - proc.wait() - - if proc.returncode != 0: - raise RuntimeError + self._check_command(cmd) output_dir = osp.join(path, 'tests', 'output', 'video') video_bag_path = osp.join(path, 'samples', 'data', '20220530173950_go_to_kitchen_rosbag.bag') cmd = 'rosrun jsk_rosbag_tools bag_to_video.py {} -o {}'.format( video_bag_path, output_dir) - proc = subprocess.Popen(cmd, shell=True) - proc.wait() - - if proc.returncode != 0: - raise RuntimeError + self._check_command(cmd) # video_to_bag.py test video_path = osp.join( @@ -90,20 +82,12 @@ def test_bag_to_video_and_video_to_bag_and_compress(self): cmd = 'rosrun jsk_rosbag_tools video_to_bag.py {} ' \ '--out {} --no-progress-bar'.format( video_path, output_filename) - proc = subprocess.Popen(cmd, shell=True) - proc.wait() - - if proc.returncode != 0: - raise RuntimeError + self._check_command(cmd) # compress_imgs.py test cmd = 'rosrun jsk_rosbag_tools compress_imgs.py {} ' \ '--no-progress-bar'.format(output_filename) - proc = subprocess.Popen(cmd, shell=True) - proc.wait() - - if proc.returncode != 0: - raise RuntimeError + self._check_command(cmd) if __name__ == '__main__': From e5fc229660471d21c7fce219e0f7b7fa402d5730 Mon Sep 17 00:00:00 2001 From: iory Date: Tue, 28 Jun 2022 02:23:12 +0900 Subject: [PATCH 39/45] [jsk_rosbag_tools] Add dependencies of download data --- jsk_rosbag_tools/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/CMakeLists.txt b/jsk_rosbag_tools/CMakeLists.txt index 84e90e216..6876f967f 100644 --- a/jsk_rosbag_tools/CMakeLists.txt +++ b/jsk_rosbag_tools/CMakeLists.txt @@ -50,5 +50,6 @@ if(CATKIN_ENABLE_TESTING) catkin_install_python(PROGRAMS ${python_test_scripts} DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) - add_rostest(tests/test_jsk_rosbag_tools.test) + add_rostest(tests/test_jsk_rosbag_tools.test + DEPENDENCIES download_audio_data download_video_data) endif() From 6fb2642bfc9248cb90ffac1a7f44b010a2958f97 Mon Sep 17 00:00:00 2001 From: iory Date: Tue, 28 Jun 2022 03:23:03 +0900 Subject: [PATCH 40/45] [jsk_rosbag_tools] Drop installation of requirements.in and requirements.txt --- jsk_rosbag_tools/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/jsk_rosbag_tools/CMakeLists.txt b/jsk_rosbag_tools/CMakeLists.txt index 6876f967f..278da82a8 100644 --- a/jsk_rosbag_tools/CMakeLists.txt +++ b/jsk_rosbag_tools/CMakeLists.txt @@ -31,11 +31,6 @@ catkin_download(download_video_data ) add_custom_target(download ALL DEPENDS download_audio_data download_video_data) -install(FILES requirements.in requirements.txt - DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} -) - - if(CATKIN_ENABLE_TESTING) find_package(catkin REQUIRED COMPONENTS roslint rostest) From 1ec14eecf61559a999e747a6bb012d0baf686f0f Mon Sep 17 00:00:00 2001 From: iory Date: Tue, 28 Jun 2022 03:28:14 +0900 Subject: [PATCH 41/45] [jsk_rosbag_tools] Split test to avoid indigo's error --- jsk_rosbag_tools/CMakeLists.txt | 6 ++ jsk_rosbag_tools/tests/test_bag_to_video.py | 66 +++++++++++++++++++ jsk_rosbag_tools/tests/test_bag_to_video.test | 7 ++ .../tests/test_jsk_rosbag_tools.py | 35 ---------- .../tests/test_jsk_rosbag_tools.test | 2 +- 5 files changed, 80 insertions(+), 36 deletions(-) create mode 100755 jsk_rosbag_tools/tests/test_bag_to_video.py create mode 100644 jsk_rosbag_tools/tests/test_bag_to_video.test diff --git a/jsk_rosbag_tools/CMakeLists.txt b/jsk_rosbag_tools/CMakeLists.txt index 278da82a8..686c46117 100644 --- a/jsk_rosbag_tools/CMakeLists.txt +++ b/jsk_rosbag_tools/CMakeLists.txt @@ -36,6 +36,7 @@ if(CATKIN_ENABLE_TESTING) set(python_test_scripts tests/test_jsk_rosbag_tools.py + tests/test_bag_to_video.py ) roslint_python() @@ -47,4 +48,9 @@ if(CATKIN_ENABLE_TESTING) add_rostest(tests/test_jsk_rosbag_tools.test DEPENDENCIES download_audio_data download_video_data) + if("$ENV{ROS_DISTRO}" STRGREATER "indigo") + # could not install moviepy on indigo. + add_rostest(tests/test_bag_to_video.test + DEPENDENCIES download_audio_data download_video_data) + endif() endif() diff --git a/jsk_rosbag_tools/tests/test_bag_to_video.py b/jsk_rosbag_tools/tests/test_bag_to_video.py new file mode 100755 index 000000000..c039cfb78 --- /dev/null +++ b/jsk_rosbag_tools/tests/test_bag_to_video.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +import os.path as osp +import subprocess +import unittest + +import rospkg +import rospy + + +PKG = 'jsk_rosbag_tools' +NAME = 'test_bag_to_video' + + +class TestBagToVideo(unittest.TestCase): + + def _check_command(self, cmd): + proc = subprocess.Popen(cmd, shell=True, + stdout=subprocess.PIPE) + with proc.stdout: + for line in iter(proc.stdout.readline, b''): + rospy.loginfo('{}'.format(line.decode('utf-8').strip())) + returncode = proc.wait() + + if returncode != 0: + raise RuntimeError('command {} failed.'.format(cmd)) + + def test_bag_to_video_and_video_to_bag_and_compress(self): + rospack = rospkg.RosPack() + path = rospack.get_path('jsk_rosbag_tools') + + output_dir = osp.join(path, 'tests', 'output', 'audio') + audio_bag_path = osp.join(path, 'samples', 'data', + '2022-05-07-hello-test.bag') + + cmd = 'rosrun jsk_rosbag_tools bag_to_video.py {} -o {}'.format( + audio_bag_path, output_dir) + self._check_command(cmd) + + output_dir = osp.join(path, 'tests', 'output', 'video') + video_bag_path = osp.join(path, 'samples', 'data', + '20220530173950_go_to_kitchen_rosbag.bag') + cmd = 'rosrun jsk_rosbag_tools bag_to_video.py {} -o {}'.format( + video_bag_path, output_dir) + self._check_command(cmd) + + # video_to_bag.py test + video_path = osp.join( + output_dir, + 'head_camera--slash--rgb--slash--throttled' + '--slash--image_rect_color--slash--compressed.mp4') + output_filename = osp.join(path, 'tests', 'output', 'video_to_bag.bag') + cmd = 'rosrun jsk_rosbag_tools video_to_bag.py {} ' \ + '--out {} --no-progress-bar'.format( + video_path, output_filename) + self._check_command(cmd) + + # compress_imgs.py test + cmd = 'rosrun jsk_rosbag_tools compress_imgs.py {} ' \ + '--no-progress-bar'.format(output_filename) + self._check_command(cmd) + + +if __name__ == '__main__': + import rostest + rostest.rosrun(PKG, NAME, TestBagToVideo) diff --git a/jsk_rosbag_tools/tests/test_bag_to_video.test b/jsk_rosbag_tools/tests/test_bag_to_video.test new file mode 100644 index 000000000..df0b7d81e --- /dev/null +++ b/jsk_rosbag_tools/tests/test_bag_to_video.test @@ -0,0 +1,7 @@ + + + + + + diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py index 65183967f..f992baf31 100755 --- a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py +++ b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py @@ -54,41 +54,6 @@ def test_merge(self): .format(audio_bag_path, audio_bag_path, output_path) self._check_command(cmd) - def test_bag_to_video_and_video_to_bag_and_compress(self): - rospack = rospkg.RosPack() - path = rospack.get_path('jsk_rosbag_tools') - - output_dir = osp.join(path, 'tests', 'output', 'audio') - audio_bag_path = osp.join(path, 'samples', 'data', - '2022-05-07-hello-test.bag') - - cmd = 'rosrun jsk_rosbag_tools bag_to_video.py {} -o {}'.format( - audio_bag_path, output_dir) - self._check_command(cmd) - - output_dir = osp.join(path, 'tests', 'output', 'video') - video_bag_path = osp.join(path, 'samples', 'data', - '20220530173950_go_to_kitchen_rosbag.bag') - cmd = 'rosrun jsk_rosbag_tools bag_to_video.py {} -o {}'.format( - video_bag_path, output_dir) - self._check_command(cmd) - - # video_to_bag.py test - video_path = osp.join( - output_dir, - 'head_camera--slash--rgb--slash--throttled' - '--slash--image_rect_color--slash--compressed.mp4') - output_filename = osp.join(path, 'tests', 'output', 'video_to_bag.bag') - cmd = 'rosrun jsk_rosbag_tools video_to_bag.py {} ' \ - '--out {} --no-progress-bar'.format( - video_path, output_filename) - self._check_command(cmd) - - # compress_imgs.py test - cmd = 'rosrun jsk_rosbag_tools compress_imgs.py {} ' \ - '--no-progress-bar'.format(output_filename) - self._check_command(cmd) - if __name__ == '__main__': import rostest diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test index b4e61b4e8..9223fa61f 100644 --- a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test +++ b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.test @@ -1,7 +1,7 @@ - From 00f043bcd1a0b8911a19f32b6c0b4210b4362bfc Mon Sep 17 00:00:00 2001 From: iory Date: Tue, 28 Jun 2022 13:55:47 +0900 Subject: [PATCH 42/45] [jsk_rosbag_tools] logerr subprocess command if failed --- jsk_rosbag_tools/tests/test_bag_to_video.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jsk_rosbag_tools/tests/test_bag_to_video.py b/jsk_rosbag_tools/tests/test_bag_to_video.py index c039cfb78..a50a7cc13 100755 --- a/jsk_rosbag_tools/tests/test_bag_to_video.py +++ b/jsk_rosbag_tools/tests/test_bag_to_video.py @@ -17,12 +17,16 @@ class TestBagToVideo(unittest.TestCase): def _check_command(self, cmd): proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + + lines = '' with proc.stdout: for line in iter(proc.stdout.readline, b''): - rospy.loginfo('{}'.format(line.decode('utf-8').strip())) + lines += line.decode('utf-8').strip() returncode = proc.wait() if returncode != 0: + rospy.logerr('{}'.format(lines)) + rospy.logerr('command {} failed.'.format(cmd)) raise RuntimeError('command {} failed.'.format(cmd)) def test_bag_to_video_and_video_to_bag_and_compress(self): From a1786d1599be316cda72e0b5fd2ae4e149215374 Mon Sep 17 00:00:00 2001 From: iory Date: Tue, 28 Jun 2022 14:28:29 +0900 Subject: [PATCH 43/45] [jsk_rosbag_tools] Specify python version --- jsk_rosbag_tools/package.xml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/jsk_rosbag_tools/package.xml b/jsk_rosbag_tools/package.xml index a53d64475..5a0dca4a7 100644 --- a/jsk_rosbag_tools/package.xml +++ b/jsk_rosbag_tools/package.xml @@ -15,13 +15,20 @@ cv_bridge ffmpeg python-moviepy-pip - python-numpy - python-rospkg-modules - python-scipy - python-setuptools - python-termcolor - python-tqdm - python-yaml + python-numpy + python-rospkg-modules + python-scipy + python-setuptools + python-termcolor + python-tqdm + python-yaml + python3-numpy + python3-rospkg-modules + python3-scipy + python3-setuptools + python3-termcolor + python3-tqdm + python3-yaml sensor_msgs roslint From fb24554d4519b3d090a5bdfefbbbf0db41c5822c Mon Sep 17 00:00:00 2001 From: iory Date: Fri, 1 Jul 2022 03:41:29 +0900 Subject: [PATCH 44/45] [jsk_rosbag_tools] Enable catkin_virtualenv for pip dependencies --- jsk_rosbag_tools/.gitignore | 1 + jsk_rosbag_tools/CMakeLists.txt | 31 +++++++++++++++++-- jsk_rosbag_tools/package.xml | 7 ++++- jsk_rosbag_tools/requirements.in | 1 + jsk_rosbag_tools/scripts/bag_to_audio.py | 0 jsk_rosbag_tools/scripts/bag_to_video.py | 0 jsk_rosbag_tools/scripts/compress_imgs.py | 0 jsk_rosbag_tools/scripts/merge.py | 0 jsk_rosbag_tools/scripts/tf_static_to_tf.py | 0 jsk_rosbag_tools/scripts/video_to_bag.py | 0 jsk_rosbag_tools/tests/test_bag_to_video.py | 0 .../tests/test_jsk_rosbag_tools.py | 0 12 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 jsk_rosbag_tools/requirements.in mode change 100755 => 100644 jsk_rosbag_tools/scripts/bag_to_audio.py mode change 100755 => 100644 jsk_rosbag_tools/scripts/bag_to_video.py mode change 100755 => 100644 jsk_rosbag_tools/scripts/compress_imgs.py mode change 100755 => 100644 jsk_rosbag_tools/scripts/merge.py mode change 100755 => 100644 jsk_rosbag_tools/scripts/tf_static_to_tf.py mode change 100755 => 100644 jsk_rosbag_tools/scripts/video_to_bag.py mode change 100755 => 100644 jsk_rosbag_tools/tests/test_bag_to_video.py mode change 100755 => 100644 jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py diff --git a/jsk_rosbag_tools/.gitignore b/jsk_rosbag_tools/.gitignore index f935021a8..571491802 100644 --- a/jsk_rosbag_tools/.gitignore +++ b/jsk_rosbag_tools/.gitignore @@ -1 +1,2 @@ !.gitignore +requirements.txt diff --git a/jsk_rosbag_tools/CMakeLists.txt b/jsk_rosbag_tools/CMakeLists.txt index 686c46117..307da0895 100644 --- a/jsk_rosbag_tools/CMakeLists.txt +++ b/jsk_rosbag_tools/CMakeLists.txt @@ -3,14 +3,32 @@ project(jsk_rosbag_tools) find_package( catkin REQUIRED + catkin_virtualenv ) +if (${catkin_virtualenv_VERSION} VERSION_LESS "0.5.1") + message(STATUS "jsk_rosbag_tools requires catkin_virtualenv >= 0.5.1") + return() +endif() + + catkin_python_setup() catkin_package( CATKIN_DEPENDS ) +if($ENV{ROS_DISTRO} STREQUAL "noetic") + catkin_generate_virtualenv( + INPUT_REQUIREMENTS requirements.in + PYTHON_INTERPRETER python3 + ) +else() + catkin_generate_virtualenv( + INPUT_REQUIREMENTS requirements.in + ) +endif() + file(GLOB SCRIPTS_FILES scripts/*) catkin_install_python( PROGRAMS ${SCRIPTS_FILES} @@ -47,10 +65,19 @@ if(CATKIN_ENABLE_TESTING) DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) add_rostest(tests/test_jsk_rosbag_tools.test - DEPENDENCIES download_audio_data download_video_data) + DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv download_audio_data download_video_data) if("$ENV{ROS_DISTRO}" STRGREATER "indigo") # could not install moviepy on indigo. add_rostest(tests/test_bag_to_video.test - DEPENDENCIES download_audio_data download_video_data) + DEPENDENCIES ${PROJECT_NAME}_generate_virtualenv download_audio_data download_video_data) endif() endif() + +install(DIRECTORY scripts samples + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} + USE_SOURCE_PERMISSIONS +) + +install(FILES requirements.txt + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +) diff --git a/jsk_rosbag_tools/package.xml b/jsk_rosbag_tools/package.xml index 5a0dca4a7..87d3eef03 100644 --- a/jsk_rosbag_tools/package.xml +++ b/jsk_rosbag_tools/package.xml @@ -11,10 +11,11 @@ catkin python-catkin-pkg-modules + catkin_virtualenv + audio_common_msgs cv_bridge ffmpeg - python-moviepy-pip python-numpy python-rospkg-modules python-scipy @@ -34,4 +35,8 @@ roslint rostest + + requirements.txt + + diff --git a/jsk_rosbag_tools/requirements.in b/jsk_rosbag_tools/requirements.in new file mode 100644 index 000000000..f7b02cd95 --- /dev/null +++ b/jsk_rosbag_tools/requirements.in @@ -0,0 +1 @@ +moviepy==1.0.3 diff --git a/jsk_rosbag_tools/scripts/bag_to_audio.py b/jsk_rosbag_tools/scripts/bag_to_audio.py old mode 100755 new mode 100644 diff --git a/jsk_rosbag_tools/scripts/bag_to_video.py b/jsk_rosbag_tools/scripts/bag_to_video.py old mode 100755 new mode 100644 diff --git a/jsk_rosbag_tools/scripts/compress_imgs.py b/jsk_rosbag_tools/scripts/compress_imgs.py old mode 100755 new mode 100644 diff --git a/jsk_rosbag_tools/scripts/merge.py b/jsk_rosbag_tools/scripts/merge.py old mode 100755 new mode 100644 diff --git a/jsk_rosbag_tools/scripts/tf_static_to_tf.py b/jsk_rosbag_tools/scripts/tf_static_to_tf.py old mode 100755 new mode 100644 diff --git a/jsk_rosbag_tools/scripts/video_to_bag.py b/jsk_rosbag_tools/scripts/video_to_bag.py old mode 100755 new mode 100644 diff --git a/jsk_rosbag_tools/tests/test_bag_to_video.py b/jsk_rosbag_tools/tests/test_bag_to_video.py old mode 100755 new mode 100644 diff --git a/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py b/jsk_rosbag_tools/tests/test_jsk_rosbag_tools.py old mode 100755 new mode 100644 From 007febbbb94b5167322c1321408e6be7c62555bd Mon Sep 17 00:00:00 2001 From: iory Date: Fri, 1 Jul 2022 04:10:11 +0900 Subject: [PATCH 45/45] [jsk_rosbag_tools] Add descriptions for static_tf_republisher.py. --- jsk_rosbag_tools/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jsk_rosbag_tools/README.md b/jsk_rosbag_tools/README.md index 3d042aca6..eb97d296b 100644 --- a/jsk_rosbag_tools/README.md +++ b/jsk_rosbag_tools/README.md @@ -156,6 +156,12 @@ optional arguments: rosrun jsk_rosbag_tools tf_static_to_tf.py $(rospack find jsk_rosbag_tools)/samples/data/20220530173950_go_to_kitchen_rosbag.bag ``` +### Note + +`jsk_topic_tools` has a [static_tf_republisher.py](https://jsk-docs.readthedocs.io/projects/jsk_common/en/latest/jsk_topic_tools/scripts/static_tf_republisher.html) which republish `/tf_static` from a rosbag file. + +`tf_static_to_tf.py` is an approach to rewrite the rosbag file. + ## merge.py Merges two bagfiles.