DOCS: object detection usecase

This commit is contained in:
Igor Brylyov 2025-02-27 16:55:58 +03:00 committed by Igor Brylev
parent 649a426c25
commit dc59211634
9 changed files with 1920 additions and 0 deletions

View file

@ -0,0 +1,17 @@
# Фреймворк: отладка.
После того как был отлажен механизм обнаружения объектов на основе метода YoloV4 и в результате его испытаний, нами было принято решение имплементировать более совершенный и современный метод Yolo 8-ой версии ([YoloV8](https://github.com/ultralytics/ultralytics)).
На основе предыдущего опыта был взят за основу lifecycle-узел ROS2, который управлялся бы исполнением действий деревьев поведения (Behavior Tree). В ходе работы над этим модулем и дальнейшей отладки выявились преимущества метода YoloV8 в сравнении с YoloV4: файл весов модели 8-й версии по размеру составил около 6 МБ, в 4-й - около 244 МБ. Также на реальных изображениях распечатанных нами моделей улучшилась их точность распознавания (рис.1). В качестве моделей мы использовали набор шахматных фигур, распечатанных на 3D-принтере.
![pic1](img/P_curve.png)
Рис.1
Когда отлаживался модуль распознавания объектов (Object Detection), выявилась сложность проектирования и отладки этого навыка. Необходимо было кроме основной логики ROS-узла создавать и отлаживать передачу параметров в дереве поведения. И была предложена схема обобщения интерфейса для любых навыков.
![Схема1](img/scheme1.jpg)
В ней предположено использовать отдельный интерфейсный узел (Interface Node), который будет реализовывать взаимодействие системы исполнения дерева поведения (BT Engine Node) c библиотекой навыков. А сами навыки предложено упаковывать в отдельные ROS-пакеты с json-файлом описания, который позволит декларативно описывать элементы интерфейса, а также схему запуска навыков.
Такой интерфейсный узел был реализован и позволил упростить как составление и выполнение дерева поведения, так и облегчить создание самой библиотеки навыков.
Первым навыком, который использовал интерфейсный узел, стала имплементация метода оценки 6D-позы объекта [DOPE](https://github.com/NVlabs/Deep_Object_Pose).

BIN
docs/img/P_curve.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
docs/img/scheme1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

File diff suppressed because one or more lines are too long

123
docs/randomization.md Normal file
View file

@ -0,0 +1,123 @@
# Рандомизация
### При обучении робота (Илья Ураев)
- Гравитация
- Положение камеры
- Конфигурацию робота (положение джоинтов так называется)
- Положение объекта или точки куда надо дотянутся
- Текстуру поверхности размещения
- Ну и я думаю чтобы с robot_builder смотрелось, то можно рандомизировать число степеней свободы робота.
- Можно рандомизировать позиции спавна робота
### При создании датасета (Александр Шушпанов)
- Зона локации (спавна) активных объектов
- Позиция камеры: радиус сферы размещения и наклон, центр этой сферы
- Источники света: количество, тип, локализация в пространстве, цветность, сила света
- Свойства материала по отражению света: зеркальность, шероховатость, металлизация, цвет
Гиперпараметры.
- количество серий (спавна активных объектов)
- количество позиций камеры на одну серию
- количество сдвигов камеры на 1 позу
### Общий список параметров рандомизации
- Положение искомого(активного) объекта(-ов) в рабочей зоне
- Позиция камеры: радиус сферы размещения и наклон, центр этой сферы
- Текстуры повехностей объектов и/или свойства материала по отражению света: зеркальность, шероховатость, металлизация, цвет
- Источники света: количество, тип, локализация в пространстве, цветность, сила света
- Конфигурация робота (положение джоинтов), число степеней свободы и его начальное расположение
- Гравитация
## Web-сервис для генерации датасетов
Для реализации пользовательского интерфейса web-сервиса нами была разработана схема описания параметров рандомизации. Её использование позволяет изменять конфигурацию параметров в зависимости от задачи. Предполагается в дальнейшем использовать модуль ввода параметров в том числе и в задачах обучения с подкреплением.
Пример такой схемы:
```
ENUM T = "ObjectDetection","PoseEstimation"
ENUM C = "","BOX","SPHERE","CAPSULE","CYLINDER","CONE","CONVEX_HULL","MESH","COMPOUND"
ENUM L = "POINT","SUN"
ENUM F = "JPEG","PNG"
MODELS = {
"id": ${ID:number:1},
"name": ${NAME:string:""},
"model": ${MODEL:string:"models/1.fbx"}
}
OBJECTS_SCENE = {
"name": ${NAME:string:""},
"collision_shape": ${enum:C:"BOX"},
"loc_xyz": [${LOC_XYZ_1:number:0}, ${LOC_XYZ_2:number:0}, ${LOC_XYZ_3:number:0}],
"rot_euler": [${ROT_EULER_1:number:0}, ${ROT_EULER_2:number:0}, ${ROT_EULER_3:number:0}],
"material_randomization": {
"specular": [${SPECULAR_1:number:0}, ${SPECULAR_2:number:1}],
"roughness": [${ROUGHNESS_1:number:0}, ${ROUGHNESS_2:number:1}],
"metallic": [${METALLIC_1:number:0}, ${METALLIC_2:number:1}],
"base_color": [
[
${BASE_COLOR_1:number:0},
${BASE_COLOR_2:number:0},
${BASE_COLOR_3:number:0},
${BASE_COLOR_4:number:1}
],
[
${BASE_COLOR_5:number:1},
${BASE_COLOR_6:number:1},
${BASE_COLOR_7:number:1},
${BASE_COLOR_8:number:1}
]
]
}
}
LIGHTS = {
"id": ${ID:number:1},
"type": ${enum:L:"POINT"},
"loc_xyz": [${LOC_XYZ_1:number:5}, ${LOC_XYZ_2:number:5}, ${LOC_XYZ_3:number:5}],
"rot_euler": [${ROT_EULER_1:number:-0.06}, ${ROT_EULER_2:number:0.61}, ${ROT_EULER_3:number:-0.19}],
"color_range_low": [${COLOR_RANGE_LOW_1:number:0.5}, ${COLOR_RANGE_LOW_2:number:0.5}, ${COLOR_RANGE_LOW_3:number:0.5}],
"color_range_high":[${COLOR_RANGE_HIGH_1:number:1}, ${COLOR_RANGE_HIGH_2:number:1}, ${COLOR_RANGE_HIGH_3:number:1}],
"energy_range":[${ENERGY_RANGE_1:number:400},${ENERGY_RANGE_2:number:900}]
}
{
"typedataset": ${enum:T:"ObjectDetection"},
"dataset_path": ${DATASET_PATH:string},
"models":${ARRAY:MODELS:[]},
"models_randomization":{
"loc_range_low": [${LOC_RANGE_LOW_1:number:-1}, ${LOC_RANGE_LOW_2:number:-1}, ${LOC_RANGE_LOW_3:number:0}],
"loc_range_high": [${LOC_RANGE_HIGH_1:number:1}, ${LOC_RANGE_HIGH_2:number:1}, ${LOC_RANGE_HIGH_3:number:2}]
},
"scene":{
"objects": ${ARRAY:OBJECTS_SCENE:[]},
"lights": ${ARRAY:LIGHTS:[]},
},
"camera_position":{
"center_shell": [${CENTER_SHELL_1:number:0}, ${CENTER_SHELL_2:number:0}, ${CENTER_SHELL_3:number:0}],
"radius_range": [${RADIUS_RANGE_1:number:0.4}, ${RADIUS_RANGE_2:number:1.4}],
"elevation_range": [${ELEVATION_RANGE_1:number:10}, ${ELEVATION_RANGE_2:number:90}]
},
"generation":{
"n_cam_pose": ${N_CAM_POSE:number:5},
"n_sample_on_pose": ${N_SAMPLE_ON_POSE:number:3},
"n_series": ${N_SERIES:number:100},
"image_format": ${enum:F:"jpg"},
"image_size_wh": [${IMAGE_SIZE_WH_1:number:640}, ${IMAGE_SIZE_WH_2:number:480}]
}
}
```
Вначале описываются перечисления - ENUM, которым присваиваются имена и список возможных значений. Затем описываются составные именованные объекты, а затем основной блок описания параметров. Этот блок представляет из себя JSON-словарь параметров, который будет подготовлен на выходе модуля ввода параметров. Каждый ключ этого словаря дополняется мини-схемой описания вводимого значения.
Формат:
```
${<имя_переменной>:<тип>:<значение_по_умолчанию>}
```
либо массив объектов
```
${ARRAY:<имя_объекта>:[]}
```
В нашем алгоритме формирования датасета для задач компьютерного зрения (ObjectDetection, PoseEstimation) выбран формат [BOP: Benchmark for 6D Object Pose Estimation](https://bop.felk.cvut.cz/home/), в его [BOP-Classic](https://bop.felk.cvut.cz/datasets/) версии.
Он содержит в своей аннотации все необходимые данные (ground truth) для обучения нейросетевых моделей поиску объектов на изображении, а также распознавания поз искомых объектов.

View file

@ -1,3 +1,377 @@
## Мета-модель фреймворка
## Соответствие Digital Twin Definition Language и мета-модели Robossembler
Слои абстракции
1. DTDL - BASETYPES, COMPLEXTYPES (Array, Map, ...), DT_TYPES
2. ROBOSSEMBLER_CONTEXT (Represents)
3. USE_CASE_CONTEXT (Entities)
4. USE_CASE_INSTANCE (Product)
## Сравнение мета-моделей Digital Twin Definition Language и ROS 2
| Сущность | DTDL | ROS | Robonomics |
| - | - | - | - |
| Описывает всё содержимое двойника, включая ниже приведённые сущности | **Interface** | **Interface Spec** | - |
| Передаваемые данные и их тип | **Telemetry** | **Topic** | **Datalog Hash** |
| Свойство, описывающее какое-то состояние двойника, может быть read-only или read/write; также описывает синхронизацию состояния между компонентами (например, показание датчика записано в облако) | **Property** | **Parameters** | **Launch Param** |
| Функция или операция, которая может быть осуществлена над двойником (например, `reboot`) | **Command** | **Service** / **Action** | **Launch** |
| Структура из примитивных типов (Array, Enum, Map, Object) данных для сериализации (в JSON, Avro, Protobuf) | **Schema** | **IDL** | - |
| Часть интерфейса (отношение part-of с каким-то другим двойником) | **Component** | - | - |
| Связь с другим цифровым двойником. Связи могут представлять различные семантические значения. Например, `contains` ("этаж содержит комнату"), `cools` ("hvac cools room"), `isBilledTo` ("счёт выставляется пользователю") | **Relationship** | - | - |
```json
# базовые типы значений (из JSON Schema)
"string","number", "boolean", "array", "object";
{
"type": "object",
"properties": {
"REPRESENT": {
"type": {
"enum": [ "OBJECT_LINK", "KEY", "FILEPATH", "VALUE", "TREE", "ARRAY", "SEQUENCE" ]
}
}
}
}
# представления
ENUM REPRESENT = "OBJECT_LINK", # строка-ссылка на объект (ENTITY)
"KEY", # уникальный ключ в виде строки (используется как идентификатор записи)
"FILEPATH", # строка - путь к файлу
"VALUE", # непосредственное значение
"TREE", # представление в виде дерева
"ARRAY", # массив значений
"SEQUENCE"; # массив ссылок на объект определённого типа
# сущности
ENUM ENTITY = "MESH", "PART", "ASSET", "BTREE", "BTACTION", "SKILL", "DATASET", "INTERFACE", "WEIGHTS", "DEVICE";
ENUM DEVICE = "ROBOT", "SENSOR";
type SCENE = {
"objects": [ { ref: "1", type: "PART" }; { ref: "2", type: "PART" }; ]
};
type PARAM = {
"sid": \${KEY:string:""},
"name": \${NAME:string:""},
"represent": \${REPRESENT:Enum<REPRESENT>:"VALUE"},
"link": \${LINK:Enum<LINK>:"topic"}
};
### тип поверхность детали
type MESH/SURFACE = {
"sid": \${KEY:string:""};
"path": { "stl": "PATH/*.stl", "brep": "PATH/*.brep", "fbx": "PATH/*.fbx", }
};
type PART = {
"sid": \${NAME:string:""},
"name": \${NAME:string:""},
"pose6d": { "loc_xyz": \${XYZ:Array<number>3:[0.0,0.0,0.0] }, "rot_xyzw": \${XYZW:Array<number>4:[0.0,0.0,0.0,1.0]} },
"attributes": [
"Robossembler_NonSolid": True
],
"surface": { "stl": "PATH/*.stl", "brep": "PATH/*.brep", },
"material": "/path/to/robossembler/materials/mmm.FCMat",
"unit_scale": \${UNIT_SCALE:number:1.0},
"dimensions": \${Array<number>3:[0.0,0.0,0.0]},
"assets": { "fbx": "PATH/*.fbx", "blend": "PATH/*.blend", }
};
type DATASET = {
"sid": \${NAME:string:""},
"name": \${NAME:string:""},
"objects": \${SEQUENCE:PART},
"environment": \${SEQUENCE:PART},
"randomisation": \${FILE}
};
type WEIGHTS = {
"sid": \${NAME:string:""},
"name": \${NAME:string:""},
"file": \${FILE:string:"*.pth"},
"epoch": \${EPOCH:number:""},
"dataset": \${OBJECT_LINK:DATASET}
};
type TOPIC = {
"sid": ...,
"name": "topic_name",
"msg": "sensor_msgs/Image",
};
DEVICE = {
"sid": 1235,
"name": "dev",
"topics": \${SEQUENCE:TOPIC},
}
// DEVICE -> TOPIC LIST -> {DEVICE: {TOPIC LIST}}
type POSE_ESTIMATION = {
"object_name": \${OBJECT_LINK:PART},
"weights": \${OBJECT_LINK:WEIGHTS},
"topic_name": \${OBJECT_LINK:TOPIC}
};
type SKILL = {
"sid": \${NAME:string:""},
"name": \${NAME:string:""},
"interface": \${INTERFACE}
};
command_LifeCycle = "run", "stop"
type ASSEMBLY_SEQUENCE = \{SEQUENCE:TASK};
# task1 = { source_state = []; target_state = [ p1 ] }
# task2 = { source_state = [ p1 ]; target_state = [ p1 p2 ] }
# task3 = { source_state = [ p1 p2 ]; target_state = [ p1 p2 p3 ] }
task = { source_state = \${TREE:PART}; target_state = \${TREE:PART} }
type TASK = {
"sid": ...
"action": \${BT_TREE}
"source_state": \${TREE:PART} // PART
"target_state": \${TREE:PART} // PRODUCT
};
type DOMAIN = {
"objects": \{SEQUENCE:PART}
"predicates": \{OBJECT_LINK:CONDITION}
"actions": \${OBJECT_LINK:BT_ACTION}
};
type BTREE = {
"sid": \${NAME:string:""},
"name": \${NAME:string:""},
};
```
## Device Package
### Camera
```json
{
"DevicePackage": { "name": "Robossembler", "version": "1.0", "format": "1" },
"Module": { "name": "RealSense Dxx", "description": "ROS Wrapper for Intel(R) RealSense(TM) Cameras" },
"Launch": { "package": "realsense2_camera", "executable": "rs_launch.py" },
"DTwin": [
{ "interface": {
"input": [
{ "name": "camera_namespace", "type": "STRING" }, // -- /robot_drawer/455_1/camera_info
{ "name": "camera_name", "type": "STRING" },
{ "name": "serial_port", "type": "STRING" },
],
"output": [
{ "name": "camera_info", "type": "TOPIC", "topic_name": "/${camera_namespace}/${camera_name}/camera_info" },
{ "name": "pose", "type": "TOPIC", "msg": "Pose" }
]
},
}
],
"Settings": [
{ "name": "camera_config", "description": "Camera Config", "type":"file", "defaultValue": "{ rgb_camera.profile: 1280x720x15 }" }
]
}
```
### Robot RBS
```json
{
"DevicePackage": { "name": "Robossembler", "version": "1.0", "format": "1" },
"Module": { "name": "RBS", "description": "Main Robot" },
"Launch": { "package": "rbs_bringup", "executable": "single_robot.launch.py" },
"DTwin": [
{ "interface": {
"input": [
{ "name": "robot_namespace", "type": "STRING", "defaultValue": "rbs_arm" },
{ "name": "dof", "type": "INT", "defaultValue": 6 }
]
}
}
],
"Settings": [
{ "name": "robot_type", "description": "Type of robot by name", "defaultValue": "rbs_arm" }
]
}
```
### Robot UR5
```json
{
"DevicePackage": { "name": "Robossembler", "version": "1.0", "format": "1" },
"Module": { "name": "UR", "description": "..." },
"Launch": { "package": "ur_package", "executable": "ur5_single_arm.py" },
"DTwin": [
{ "interface": {
"input": [
{ "robot_namespace": "robot1" },
],
},
}
],
"Settings": [
{ "name": "", "description": "Config", "type":"file", "defaultValue": "{}" }
]
}
```
```json
{
"SkillPackage": { "name": "Robossembler", "version": "1.0", "format": "1" },
"Module": { "name": "PoseEstimation", "description": "Pose Estimation skill with DOPE" },
"Launch": { "package": "rbs_perception", "executable": "pe_dope_lc.py", "name": "lc_dope" },
"BTAction": [
{ "name": "peConfigure",
"type": "run",
"interface": {
"input": [
{ "name": "image_raw", "type": "TOPIC", "msg": "Image" },
{ "name": "camera_info", "type": "TOPIC", "msg": "CameraInfo" },
{ "name": "object_name", "type": "PART", "msgs": "Part" }, // string
{ "name": "weights", "type": "WEIGHTS", "msgs": "" },
],
"output": [
{ "name": "pose_estimation", "type": "TOPIC" },
]
},
},
{ "name": "peStop", "type": "stop", "interface": { "input": [], "output": [] } }
],
"Settings": [
{ "name": "publishDelay", "description": "Publish Delay", "type":"float", "defaultValue": 0.5 },
{ "name": "tf2_send_pose", "description": "Transform Pose", "type":"int", "defaultValue": 1 },
{ "name": "mesh_scale", "description": "Part Mesh Scale", "type":"float", "defaultValue": 0.001 }
]
}
```
## Атомарные навыки
```xml
<TreeNodesModel>
<SubTree ID="GraspObject">
<input_port default="false" name="__shared_blackboard"></input_port>
<input_port name="pose_vec"/>
<input_port name="robot_name"/>
</SubTree>
<Action ID="GripperAction">
<input_port name="action">Open or close</input_port>
<input_port name="gripper_name"/>
</Action>
<SubTree ID="InspectWorkspace">
<input_port default="true" name="__shared_blackboard">
<input_port name="pose_vec"/>
<input_port name="robot_name"/>
</SubTree>
<Condition ID="IsObjectInWorkspace">
<input_port name="topic_name"/>
</Condition>
<Condition ID="IsObjectVisible"/>
<Action ID="MoveToPose">
<input_port name="cancel_timeout"/>
<input_port name="pose"/>
<input_port name="robot_name"/>
<input_port name="server_name"/>
<input_port name="server_timeout"/>
</Action>
<Action ID="MoveToPoseArray">
<input_port name="cancel_timeout"/>
<input_port name="pose_vec_in"/>
<output_port name="pose_vec_out"/>
<input_port name="robot_name"/>
<input_port name="server_name"/>
<input_port name="server_timeout"/>
</Action>
<SubTree ID="PlaceObject">
<input_port default="true" name="__shared_blackboard"></input_port>
<input_port name="pose_vec"/>
<input_port name="robot_name"/>
</SubTree>
<Action ID="PoseEstimationCleanup"/>
<Action ID="PoseEstimationConfigure">
<input_port name="object_name"/>
<output_port name="topic_name"/>
<input_port name="weight_file"/>
</Action>
<Action ID="RbsBtAction">
<input_port name="command"/>
<input_port name="do"/>
<input_port name="server_name"/>
<input_port name="server_timeout"/>
</Action>
</TreeNodesModel>
```
## Пример интерфейса в DTDL
```json
{
"@context": "...;3",
"@id": "robossember_assembly;1",
"@type": "Interface",
"displayName": "Bolt",
"contents": [
{
"@type": "Property",
"name": "Label",
"schema": "string"
},
{
"@type": "Property",
"name": "mesh",
"writable": true,
"schema": "string"
},
{
"@type": "Telemetry",
"name": "Pose6D",
"writable": true,
"schema": {
"@type": "Object",
"fields": [
{
"name": "x",
"schema": "double"
},
{
"name": "y",
"schema": "double"
},
{
"name": "z",
"schema": "double"
}
]
}
},
{
"@type": "Relationship",
"name": "contains",
"target": "dtmi:com:robossember_assembly:Bolt;1"
},
{
"@type": "Component",
"name": "frontCamera",
"schema": "dtmi:com:example:Camera;3"
},
]
}
```
## Пример файла описания сцены из Blender
TODO: рассписать потребность в основных элементах, что и для чего

View file

@ -10,3 +10,7 @@
Надстройка для ArgumentParser.
Позволяет использовать передачу аргументов командной строки для Bledner и FreeCAD.
### get_interfaces.py
Получение информации о ROS2-топиках в пакете цифрового двойника, навыка.

68
utils/get_interfaces.py Normal file
View file

@ -0,0 +1,68 @@
import argparse
import os
import json
import subprocess
import signal
import time
from ros2cli.node.strategy import NodeStrategy
from ros2topic.api import get_topic_names_and_types
OUTPUT_FILE = "topics.json"
TOPICS_FILTER = ["/parameter_events", "/rosout"]
def get_script_args(cfg):
args = cfg["command"].split()
args.append(cfg["package"])
args.append(cfg["executable"])
return args
def get_topics(filename, path):
jsonpath = os.path.join(path, filename)
with NodeStrategy({}) as node:
topic_names_and_types = get_topic_names_and_types(node=node, include_hidden_topics=False)
topic_info = []
for (topic_name, topic_types) in topic_names_and_types:
if not topic_name in TOPICS_FILTER:
topic_info.append({"name": topic_name, "type": topic_types[0]})
print(f"---> number of topics: {len(topic_info)}")
j_data = {"topics": topic_info}
with open(jsonpath, "w") as fh:
json.dump(j_data, fh, indent=2)
for topic in topic_info:
print(topic["name"])
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--package", required=True, help="Json-string/file with package parameters")
parser.add_argument("--path", default="", help="Output path")
parser.add_argument("--json", default=OUTPUT_FILE, help="Output file name in json-format")
parser.add_argument('--delay', default=5, type=int, help="Delay in seconds")
args = parser.parse_args()
if args.package[-5:] == ".json":
if not os.path.isfile(args.package):
print(f"Error: no such file '{args.package}'")
exit(-1)
with open(args.package, "r") as f:
j_data = f.read()
else:
j_data = args.package
try:
cfg = json.loads(j_data)
except json.JSONDecodeError as e:
print(f"JSon error: {e}")
exit(-2)
cmd = get_script_args(cfg)
process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
time.sleep(args.delay)
get_topics(args.json, args.path)
process.send_signal(signal.SIGINT)

View file

@ -0,0 +1,72 @@
"""
ROS2_Topic_to_json
ROS 2 program for outputting system objects to json format
From https://github.com/ros2/ros2cli/blob/humble/ros2topic/ros2topic/verb/list.py
@shalenikol release 0.1
"""
"""
usage: python ros2_topic_to_json.py [-h] [--jsonpath JSONPATH]
options:
-h, --help show this help message and exit
--jsonpath JSONPATH Output file in json-format
"""
import argparse
import json
from ros2cli.node.strategy import NodeStrategy
from ros2topic.api import get_topic_names_and_types
OUTPUT_FILE = "topics.json"
# def show_topic_info(topic_info, is_publisher):
# message = ('Published' if is_publisher else 'Subscribed') + ' topics:\n'
# for (topic_name, topic_types, pub_count, sub_count) in topic_info:
# count = pub_count if is_publisher else sub_count
# if count:
# topic_types_formatted = ', '.join(topic_types)
# count_str = str(count) + ' ' + ('publisher' if is_publisher else 'subscriber') \
# + ('s' if count > 1 else '')
# message += f' * {topic_name} [{topic_types_formatted}] {count_str}\n'
# return message
def main(args, jsonpath):
topic_info = []
with NodeStrategy(args) as node:
topic_names_and_types = get_topic_names_and_types(
node=node,
include_hidden_topics=False)
for (topic_name, topic_types) in topic_names_and_types:
# if args.verbose:
# pub_count = node.count_publishers(topic_name)
# sub_count = node.count_subscribers(topic_name)
# topic_info.append((topic_name, topic_types, pub_count, sub_count))
# else:
topic_info.append((topic_name, topic_types))
# if args.count_topics:
print(f"---> number of topics: {len(topic_names_and_types)}")
j_data = {"topics": topic_info}
with open(jsonpath, "w") as fh:
json.dump(j_data, fh, indent=2)
# elif topic_names_and_types:
# if args.verbose:
# print(show_topic_info(topic_info, is_publisher=True))
# print(show_topic_info(topic_info, is_publisher=False))
# else:
for (topic_name, topic_types) in topic_info:
msg = "{topic_name}"
# topic_types_formatted = ', '.join(topic_types)
# if args.show_types:
# msg += ' [{topic_types_formatted}]'
print(msg.format_map(locals()))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--jsonpath", default=OUTPUT_FILE, help="Output file in json-format")
m_args = parser.parse_args()
args = {}
main(args, m_args.jsonpath)