diff --git a/docs/framework_debug_3part.md b/docs/framework_debug_3part.md new file mode 100644 index 0000000..5bdba4b --- /dev/null +++ b/docs/framework_debug_3part.md @@ -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). diff --git a/docs/img/P_curve.png b/docs/img/P_curve.png new file mode 100644 index 0000000..f2a2125 Binary files /dev/null and b/docs/img/P_curve.png differ diff --git a/docs/img/scheme1.jpg b/docs/img/scheme1.jpg new file mode 100644 index 0000000..63c3988 Binary files /dev/null and b/docs/img/scheme1.jpg differ diff --git a/docs/obj_detection_use_case.drawio b/docs/obj_detection_use_case.drawio new file mode 100644 index 0000000..d0a3322 --- /dev/null +++ b/docs/obj_detection_use_case.drawio @@ -0,0 +1,1262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/randomization.md b/docs/randomization.md new file mode 100644 index 0000000..e065e77 --- /dev/null +++ b/docs/randomization.md @@ -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) для обучения нейросетевых моделей поиску объектов на изображении, а также распознавания поз искомых объектов. \ No newline at end of file diff --git a/docs/scene_generator.md b/docs/scene_generator.md index bf29982..85df89c 100644 --- a/docs/scene_generator.md +++ b/docs/scene_generator.md @@ -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:"VALUE"}, + "link": \${LINK:Enum:"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:Array3:[0.0,0.0,0.0] }, "rot_xyzw": \${XYZW:Array4:[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": \${Array3:[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 + + + + + + + + Open or close + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +## Пример интерфейса в 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: рассписать потребность в основных элементах, что и для чего diff --git a/utils/README.md b/utils/README.md index ad47658..68336c1 100644 --- a/utils/README.md +++ b/utils/README.md @@ -10,3 +10,7 @@ Надстройка для ArgumentParser. Позволяет использовать передачу аргументов командной строки для Bledner и FreeCAD. + +### get_interfaces.py + +Получение информации о ROS2-топиках в пакете цифрового двойника, навыка. \ No newline at end of file diff --git a/utils/get_interfaces.py b/utils/get_interfaces.py new file mode 100644 index 0000000..1b83915 --- /dev/null +++ b/utils/get_interfaces.py @@ -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) diff --git a/utils/ros2_topic_to_json.py b/utils/ros2_topic_to_json.py new file mode 100644 index 0000000..ffb1630 --- /dev/null +++ b/utils/ros2_topic_to_json.py @@ -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) \ No newline at end of file