-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
In summary: * convert_rosbag2: Add new flexible format for parsing conversion config files * convert_rosbag2: Enable saving compressed PNG / JPEG images ----- In more details: Includes the following changes to the `convert_rosbag2` script: * Introduce a new "flexible" (contrary to the pre-existing rigid) format for specifying the topics and Slamcore subdirectories for conversion. The new version allows conversion of rosbags using a config file like the following: ```jsonc { // conversion metadata "conversion_meta": { "format": "flexible", "version": 1 }, // SLAM Input topics "cam0": { "topics": ["/slamcore/visible_0/image_raw", "/slamcore/ir_0/image_raw"] }, "cam1": { "topics": ["/slamcore/visible_1/image_raw", "/slamcore/ir_1/image_raw"] }, "imu0": { "topic": "/irrelevant_imu" }, "odometry0": { "topic": "/slamcore/odom", "required": false }, "groundtruth0": { "topic": "/irrelevant_gts" } } ``` The above will look for a topic named `/slamcore/visible_0/image_raw` and if it can't find it it will fall back to the second specified topic name, in the `cam0` case (`/slamcore/ir_0/image_raw`). It will give an error if it can't match any of the specified topics, unless the user has specified the `required: false` key. * Introduce a new `--disable-progress-bar` CLI flag and app argument. This way, the script won't produce unwanted noisy standard output/error when executed automatically (as we do in `dataset-analysis`). This also sometimes led to errors uploading reports to Slack as the text report was too big to be uploaded as a code snippet. * Introduce a convert_rosbag2 configuration struct - `Config` which captures all the configuration variables that the script needs. This allows building this struct and calling the `convert_rosbag2` function without having to call it via a subprocess in `dataset-analysis` * Only convert topics if they contain at least one message. Otherwise this may lead to errors. E.g., during trajectory alighnment, if there's an optimized trajectory nav_path topic, we'll use that as the optimized trajectory. However, the GT alignment will fail if the opitmized trajectory data.csv file is empty. In that case we should altogether have discarded that topic in the first place Also includes: Fix small typo in var name -> `verbotsity_to_logging_lvl` -> `verbosity_to_logging_lvl` Also includes: * Solve progress bar race condition In case where the rosbag we're converting is short, we may end up joining the Image queue faster than the time it takes for the progress bar to update the standard output. In that case the program exits but daemonic thread on which we call `progress_bar.update()` is still active and will attempt to access a progress_bar instance that has gone out of scope. To solve that, we're calling `progress_bar.update()` before releasing the imq lock (via `imgq.task_done()`)
- Loading branch information
1 parent
36243f0
commit a8d2ab2
Showing
49 changed files
with
1,147 additions
and
746 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
from enum import Enum, auto | ||
from typing import Any, Dict, Tuple | ||
from slamcore_utils.logging import logger | ||
|
||
|
||
class ConversionFormat(Enum): | ||
"""Conversion formats supported. | ||
These define the way we parse the conversion configuration file. | ||
""" | ||
|
||
RIGID = auto() | ||
FLEXIBLE = auto() | ||
|
||
@staticmethod | ||
def from_name(name: str) -> "ConversionFormat": | ||
return _names_to_conversion_formats[name] | ||
|
||
def __str__(self) -> str: | ||
return self.name.lower() | ||
|
||
def __repr__(self) -> str: | ||
return f"{self.__class__.__name__}.{self.name}" | ||
|
||
@classmethod | ||
def detect_format(cls, json: Dict[str, Any]) -> Tuple["ConversionFormat", int]: | ||
""" | ||
Return a tuple of (conversion_format, version_format) by parsing the json contents | ||
provided. Always resort to the rigid format in case of errors. | ||
Do remove the conversion meta section from the JSON contents at the end of this call. | ||
""" | ||
conversion_meta_section = "conversion_meta" | ||
meta_section = json.pop(conversion_meta_section, None) | ||
|
||
if meta_section is None: | ||
logger.warning( | ||
f"No {conversion_meta_section} section. " | ||
f"Resorting to {ConversionFormat.RIGID} conversion format ..." | ||
) | ||
return (ConversionFormat.RIGID, 0) | ||
|
||
try: | ||
format_str = meta_section["format"] | ||
version_int = int(meta_section["version"]) | ||
except Exception as e: | ||
raise RuntimeError( | ||
f"Invalid {conversion_meta_section} section. Cannot continue." | ||
) from e | ||
|
||
try: | ||
format_and_version = cls.from_name(format_str), version_int | ||
except KeyError as e: | ||
raise RuntimeError(f"Unknown conversion format -> {format_str}.") from e | ||
|
||
logger.info(f"Determined conversion format/version: {format_and_version} .") | ||
return format_and_version | ||
|
||
|
||
_conversion_formats_to_names = {cf: str(cf) for cf in ConversionFormat} | ||
_names_to_conversion_formats = {v: k for k, v in _conversion_formats_to_names.items()} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
from functools import reduce | ||
from pathlib import Path | ||
from runpy import run_path | ||
from slamcore_utils.logging import logger | ||
from typing import Sequence, cast | ||
import operator | ||
import pkg_resources | ||
|
||
from .ros2_converter_plugin import Ros2ConverterPlugin | ||
|
||
|
||
def get_internal_plugins_dir() -> Path: | ||
""" | ||
Get the path to the internal ROS 2 plugins. | ||
This depends on whether the user has installed this package and executed the installed | ||
script or whether they've directly executed the script via `python3 -m` | ||
""" | ||
if __package__: | ||
return ( | ||
Path(pkg_resources.resource_filename(__package__.split(".")[0], "ros2")) | ||
/ "ros2_converter_plugins" | ||
) | ||
else: | ||
return Path(__file__).absolute().parent / "ros2_converter_plugins" | ||
|
||
|
||
def load_converter_plugins_from_multiple_files( | ||
converter_plugin_paths: Sequence[Path], | ||
raise_on_error: bool = True, | ||
) -> Sequence[Ros2ConverterPlugin]: | ||
if converter_plugin_paths: | ||
converter_plugins = cast( | ||
Sequence[Ros2ConverterPlugin], | ||
reduce( | ||
operator.concat, | ||
( | ||
load_converter_plugins(plugin_path, raise_on_error=raise_on_error) | ||
for plugin_path in converter_plugin_paths | ||
), | ||
), | ||
) | ||
else: | ||
return [] | ||
|
||
# Sanity check, each one of converter_plugins var is of the right type | ||
for cp in converter_plugins: | ||
if not isinstance(cp, Ros2ConverterPlugin): | ||
raise RuntimeError( | ||
"One of the specified converter plugins is not of type " | ||
"Ros2ConverterPlugin, cannot proceed" | ||
) | ||
|
||
return converter_plugins | ||
|
||
|
||
def load_converter_plugins( | ||
plugin_path: Path, raise_on_error: bool | ||
) -> Sequence[Ros2ConverterPlugin]: | ||
"""Load all the ROS 2 Converter Plugins specified in the given plugin python module. | ||
In case of errors print the right error messages acconrdingly. | ||
""" | ||
logger.debug(f"Loading ROS 2 converter plugin from {plugin_path} ...") | ||
try: | ||
ns_dict = run_path(str(plugin_path)) | ||
except BaseException as e: | ||
e_str = ( | ||
"Failed to load ROS 2 converter plugin from " | ||
f"{plugin_path.relative_to(plugin_path.parent)}\n\nError: {e}\n\n" | ||
) | ||
if raise_on_error is True: | ||
raise RuntimeError(f"{e_str}Exiting ...") from e | ||
else: | ||
logger.debug(e_str) | ||
return [] | ||
|
||
# Sanity check, specified converter_plugins var | ||
if "converter_plugins" not in ns_dict: | ||
raise RuntimeError( | ||
f"No converter plugins were exported in specified plugin -> {plugin_path}\n", | ||
( | ||
'Make sure that you have initialized a variable named "converter_plugins" ' | ||
"at the top-level of the said plugin." | ||
), | ||
) | ||
|
||
converter_plugins = ns_dict["converter_plugins"] | ||
|
||
# Sanity check, converter_plugins var is of the right type | ||
if not isinstance(converter_plugins, Sequence): | ||
raise RuntimeError( | ||
f"Found the converter_plugins at the top-level of the plugin ({plugin_path}) " | ||
"but that variable is not of type Sequence. Its value is " | ||
f"{converter_plugins} and of type {type(converter_plugins)}" | ||
) | ||
|
||
return converter_plugins |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.