### Launch ROS2 TurtleSim Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/services.md Start the turtle simulation package to test service communication. ```bash # make sure service server is started # for this example we use the turtle sim package ros2 launch turtlesim multisim.launch.py ``` -------------------------------- ### Create ROS2 Publisher Nodes in C++ Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/services.md Example of creating publisher nodes using full and short class names. ```cpp // example with full class name auto spawn1 = rosNode->CreateAndAddPublisherNode("vtkMRMLROS2ServiceClientSpawnNode", "/turtlesim1/spawn"); --- pubString->Publish("my first string"); // example with short class name, Spawn will be expended to vtkMRMLROS2ServiceClientSpawnNode auto spawn2 = rosNode->CreateAndAddPublisherNo("Spawn", "/turtlesim2/spawn"); ``` -------------------------------- ### Install Required Python Packages Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/compilation.md Installs Sphinx for documentation generation and other project dependencies from requirements.txt. ```sh pip install sphinx pip install -r requirements.txt ``` -------------------------------- ### Setup ROS2 parameter observation in C++ Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/parameters.md Initializes a parameter node and attaches an observer for parameter modification events. ```cpp // setup to get parameter robot_description from node state_publisher auto parameterNode = rosNode->CreateAndAddParameterNode("state_publisher"); parameterNode->AddParameter("robot_description"); // add a callback parameterNode->AddObserver(vtkMRMLROS2ParameterNode::ParameterModifiedEvent, this, &myClassType::Callback); ``` -------------------------------- ### Launch Robot State Publisher for Phantom Omni Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/robot-visualization.md Use this command to start the robot state publisher for the Phantom Omni robot. Ensure the sensable_omni_model package is built. ```bash ros2 launch sensable_omni_model omni.launch.py ``` -------------------------------- ### Create String Publisher in C++ Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/topics.md Example of creating a string publisher using its full class name and publishing a string in C++. A ROS2 subscriber should be active on the topic. ```cpp // example with full class name auto pubString = rosNode->CreateAndAddPublisherNode("vtkMRMLROS2PublisherStringNode", "/my_string"); // run ros2 topic echo /my_string in a terminal to see the output pubString->Publish("my first string"); ``` -------------------------------- ### Create and Publish String Message Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/topics.md Example of creating a string publisher and publishing a message. Ensure a ROS2 subscriber is running on the specified topic to see the output. ```python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # optional, shows which publishers are available rosNode.RegisteredROS2PublisherNodes() # example with full class name pubString = rosNode.CreateAndAddPublisherNode('vtkMRMLROS2PublisherStringNode', '/my_string') # run `ros2 topic echo /my_string` in a terminal to see the output pubString.Publish('my first string') ``` -------------------------------- ### Start myCobot Real-time Listener Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/robot-visualization.md Run this Python script to listen to the real myCobot device. You may need to adjust the port on line 14. Ensure the robot is in Transponder Mode. ```python cd ~/ros2_ws/src/mycobot_ros2/src/mycobot_ros2/mycobot_280/mycobot_280/config python3 listen_real.py ``` -------------------------------- ### Run Dummy Joint State Publisher for Phantom Omni Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/robot-visualization.md Execute this command in a separate terminal to start a dummy joint state publisher for the Phantom Omni, allowing visualization of arm movement. This script emulates robot joint positions. ```bash ros2 run sensable_omni_model pretend_omni_joint_state_publisher ``` -------------------------------- ### Access Default ROS2 Node in C++ (External Module) Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/nodes.md Access the default ROS2 node from an external module in C++ by first getting the ROS2 module logic. Ensure the ROS2 module is loaded. ```cpp vtkMRMLAbstractLogic * logic = this->GetModuleLogic("ROS2"); if (logic == nullptr) { vtkErrorMacro(<< "ROS2 logic not found"); } else { vtkSlicerROS2Logic * rosLogic = vtkSlicerROS2Logic::SafeDownCast(logic); if (rosLogic == nullptr) { vtkErrorMacro(<< "Found what should be the default ROS2 logic but the type is wrong"); } else { vtkMRMLROS2NodeNode * rosNode = rosLogic->GetDefaultROS2Node(); // now we can use the node } } ``` -------------------------------- ### Get Registered ROS2 Publisher/Subscriber Nodes Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/topics.md Retrieve the default ROS2 node and list available publisher or subscriber classes. This is useful for understanding supported message types. ```python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() rosNode.RegisteredROS2PublisherNodes() rosNode.RegisteredROS2SubscriberNodes() ``` -------------------------------- ### Clone Slicer ROS 2 Module Repository Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/getting-started.md Clone the Slicer ROS 2 module repository into your ROS 2 workspace's src directory. Ensure you have sourced your ROS 2 setup script first. ```bash source /opt/ros/galactic/setup.bash mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src git clone https://github.com/rosmed/slicer_ros2_module ``` -------------------------------- ### Get Registered ROS2 Publisher/Subscriber/Service Types in Python Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Retrieve lists of all registered ROS2 publisher, subscriber, and service client node types available in the Slicer ROS2 module. This helps in understanding the supported message type mappings. ```Python # Supported publisher/subscriber types: # Short Name | Slicer Type | ROS Type # ----------------|--------------------------|---------------------------------- # String | std::string | std_msgs/msg/String # Bool | bool | std_msgs/msg/Bool # Int | int | std_msgs/msg/Int64 # Double | double | std_msgs/msg/Float64 # IntArray | vtkIntArray | std_msgs/msg/Int64MultiArray # DoubleArray | vtkDoubleArray | std_msgs/msg/Float64MultiArray # IntTable | vtkTable | std_msgs/msg/Int64MultiArray # DoubleTable | vtkTable | std_msgs/msg/Float64MultiArray # Pose | vtkMatrix4x4 | geometry_msgs/msg/Pose # WrenchStamped | vtkDoubleArray | geometry_msgs/msg/WrenchStamped # PoseArray | vtkTransformCollection | geometry_msgs/msg/PoseArray # UInt8Image | vtkUInt8Array | sensor_msgs/msg/Image # PointCloud | vtkPoints | sensor_msgs/msg/PointCloud # Example: Get list of all registered types rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() print("Publishers:", rosNode.RegisteredROS2PublisherNodes()) print("Subscribers:", rosNode.RegisteredROS2SubscriberNodes()) print("Services:", rosNode.RegisteredROS2ServiceClientNodes()) ``` -------------------------------- ### Create and Use ROS2 Service Clients in Python Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/services.md Demonstrates creating service clients, sending asynchronous requests, and retrieving responses. ```python # create clients rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # optional, shows which services are available rosNode.RegisteredROS2ServiceClientNodes() # example with full class name spawn1 = rosNode.CreateAndAddServiceClientNode('vtkMRMLROS2ServiceClientSpawnNode', '/turtlesim1/spawn') # to check if the client is started, in a shell: ros2 service info /turtlesim1/spawn # now create a blank request, a new turtle will be created req = spawn1.CreateBlankRequest() req.SetX(4.0) req.SetY(4.0) # send the request spawn1.SendAsyncRequest(req) # get the response res = spawn1.GetLastResponse() # Spawn request returns the name given to the new turtle res.GetName() # example with short class name, Spawn will be expended to vtkMRMLROS2ServiceClientSpawnNode spawn2 = rosNode.CreateAndAddServiceClientNode('Spawn', '/turtlesim2/spawn') ``` -------------------------------- ### Navigate to Slicer build directory and launch Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/getting-started.md Changes the working directory to the Slicer build folder and executes the Slicer binary. ```bash cd ~/something_something/Slicer-SuperBuild/Slicer-build ./Slicer ``` -------------------------------- ### Create and use a Tf2 lookup Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/tf2.md Demonstrates creating a lookup node and retrieving the transformation matrix. ```python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # Note that this next line will give you a repeated error if there is nothing broadcasting to Parent -> Child lookupNode = rosNode.CreateAndAddTf2LookupNode('Parent', 'Child') # get the transform "manually" lookupMat = lookupNode.GetMatrixTransformToParent() # or use an observer observerId = lookupNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, observer.Callback) ``` ```cpp auto lookup = rosNode.CreateAndAddTf2LookupNode("Parent", "Child"); # Broadcast a 4x4 matrix vtkSmartPointer lookupMat = vtkMatrix4x4::New(); lookupMat->GetMatrixTransformToParent(lookupMat); ``` -------------------------------- ### Get Default ROS2 Node in Python Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/nodes.md Retrieve the default ROS2 node in Slicer using Python. This node is essential for interacting with ROS functionalities. ```python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() ``` -------------------------------- ### Compile HTML Documentation Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/compilation.md Builds the HTML version of the documentation using the make command. ```sh make html ``` -------------------------------- ### Create and use a Tf2 broadcaster Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/tf2.md Demonstrates initializing a broadcaster and sending a 4x4 matrix transform. ```python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() broadcaster = rosNode.CreateAndAddTf2BroadcasterNode('Parent', 'Child') # Broadcast a 4x4 matrix broadcastedMat = vtk.vtkMatrix4x4() broadcastedMat.SetElement(0, 3, 66.0) # Set a default value broadcaster.Broadcast(broadcastedMat) ``` ```cpp auto broadcaster = rosNode.CreateAndAddTf2BroadcasterNode("Parent", "Child"); # Broadcast a 4x4 matrix vtkSmartPointer broadcastedMat = vtkMatrix4x4::New(); broadcastedMat->SetElement(0, 3, 66.0); broadcaster->Broadcast(broadcastedMat); ``` -------------------------------- ### Access ROS2 parameters in Python Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/parameters.md Demonstrates creating a parameter node, adding a parameter to observe, and retrieving its value after verifying existence and type. ```python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # setup to get parameter robot_description from node state_publisher parameterNode = rosNode.CreateAndAddParameterNode('state_publisher') parameterNode.AddParameter('robot_description') # check if parameter is set, C++ example is more detailed if parameterNode.IsParameterSet('robot_description'): # then check the type if parameterNode.GetParameterType('robot_description') == 'string': robotDescription = parameterNode.GetParameterAsString('robot_description') ``` -------------------------------- ### Create String Publisher using Short Name in C++ Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/topics.md Demonstrates creating a string publisher using its short name in C++. The 'String' short name is expanded to 'vtkMRMLROS2PublisherStringNode'. ```cpp // example with short class name, String will be expended to vtkMRMLROS2PublisherStringNode auto pubString2 = rosNode->CreateAndAddPublisherNode("String", "/my_second_string"); ``` -------------------------------- ### Launch dVRK Arm with Specific Parameters Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/robot-visualization.md This command launches the dVRK arm model, specifying the arm type as PSM1 and generation as Classic. Ensure the dvrk_model package is compiled. ```bash source ~/ros2_ws/install/setup.bash ros2 launch dvrk_model arm.launch arm:=PSM1 generation:=Classic ``` -------------------------------- ### Create and Publish Matrix Message using Short Name Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/topics.md Demonstrates creating a publisher for a matrix type using its short name and publishing a modified matrix. The 'Pose' short name is expanded to 'vtkMRMLROS2PublisherPoseNode'. ```python # example with short class name, Pose will be expended to vtkMRMLROS2PublisherPoseNode pubMatrix = rosNode.CreateAndAddPublisherNode('Pose', '/my_matrix') # run `ros2 topic echo /my_matrix` in a terminal to see the output mat = pubMatrix.GetBlankMessage() # returns a vtkMatrix4x4 mat.SetElement(0, 3, 3.1415) # Modify the matrix so we can see something changing pubMatrix.Publish(mat) ``` -------------------------------- ### Launch Simulated dVRK Arm Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/robot-visualization.md This command launches a simulated dVRK arm, specifying the arm type as PSM1 and generation as Classic. It requires building the dvrk_model package. ```bash ros2 launch dvrk_model arm.launch arm:=PSM1 generation:=Classic simulated:=False ``` -------------------------------- ### Create and Use Parameter Clients in Python Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Monitor ROS 2 node parameters from Slicer. A parameter node observes all parameters from a single ROS node. Ensure a node with parameters is running in ROS 2. ```python # Python - Create and use parameter clients # First start a node with parameters: ros2 run turtlesim turtlesim_node rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # Create a parameter node to monitor turtlesim's parameters paramNode = rosNode.CreateAndAddParameterNode('/turtlesim') # Specify which parameters to monitor paramNode.AddParameter('background_r') paramNode.AddParameter('background_g') paramNode.AddParameter('background_b') # Wait for parameters to be retrieved (spin happens automatically) import time time.sleep(0.5) # Check if parameter exists and get its value if paramNode.IsParameterSet('background_r'): paramType = paramNode.GetParameterType('background_r') # Returns 'integer' if paramType == 'integer': bgRed = paramNode.GetParameterAsInteger('background_r') print(f"Background red: {bgRed}") # Get parameter as human-readable string (works for any type) paramInfo = paramNode.PrintParameter('background_r') # Parameter types and accessors: # 'bool' -> GetParameterAsBool(name) # 'integer' -> GetParameterAsInteger(name) # 'double' -> GetParameterAsDouble(name) # 'string' -> GetParameterAsString(name) # 'bool_array' -> GetParameterAsVectorOfBools(name) # 'integer_array' -> GetParameterAsVectorOfIntegers(name) # 'double_array' -> GetParameterAsVectorOfDoubles(name) # 'string_array' -> GetParameterAsVectorOfStrings(name) # Add observer for parameter changes def onParameterChanged(caller=None, event=None): if paramNode.IsParameterSet('background_g'): print(f"Green changed to: {paramNode.GetParameterAsInteger('background_g')}") paramNode.AddObserver(slicer.vtkMRMLROS2ParameterNode.ParameterModifiedEvent, onParameterChanged) # Clean up rosNode.RemoveAndDeleteParameterNode('/turtlesim') ``` -------------------------------- ### Compile SlicerIGT and SlicerIGSIO extensions Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/limitations.md Builds SlicerIGT and its dependency SlicerIGSIO from source using CMake. Ensure the Slicer_DIR path is updated to match your local Slicer build directory. ```bash git clone https://github.com/SlicerIGT/SlicerIGT.git mkdir SlicerIGT-build git clone https://github.com/IGSIO/SlicerIGSIO.git mkdir SlicerIGSIO-build cd SlicerIGSIO-build cmake ../SlicerIGSIO -DSlicer_DIR:PATH=/home/your_user_name_here/something_something/Slicer-SuperBuild-Debug/Slicer-build/ make cd ../ cd SlicerIGT-build cmake ../SlicerIGT -DSlicer_DIR:PATH=/home/your_user_name_here/something_something/Slicer-SuperBuild-Debug/Slicer-build/ \ -DSlicerIGSIO_DIR:PATH=/home/your_user_name_here/something_something/SlicerIGSIO-build/inner-build/ make ``` -------------------------------- ### Create and Use Service Clients in Python Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Use this to call ROS 2 services from Slicer. Ensure a service server is running in ROS 2 before execution. The response is retrieved after the request is sent. ```python # Python - Create and use service clients # First start a service server: ros2 run turtlesim turtlesim_node rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # Create a spawn service client for turtlesim spawnClient = rosNode.CreateAndAddServiceClientNode('vtkMRMLROS2ServiceClientSpawnNode', '/spawn') # Create and configure the request request = spawnClient.CreateBlankRequest() request.SetX(5.0) request.SetY(5.0) request.SetTheta(1.57) # 90 degrees request.SetName('my_turtle') # Send the request asynchronously spawnClient.SendAsyncRequest(request) # Wait for response (spin is automatic, or call rosLogic.Spin()) import time time.sleep(0.5) # Get the response response = spawnClient.GetLastResponse() turtleName = response.GetName() print(f"Spawned turtle: {turtleName}") # Use observer for async notification def onServiceResponse(caller=None, event=None): res = spawnClient.GetLastResponse() print(f"Service completed, turtle name: {res.GetName()}") serviceObserverId = spawnClient.AddObserver('ModifiedEvent', onServiceResponse) # Clean up rosNode.RemoveAndDeleteServiceClientNode('/spawn') ``` -------------------------------- ### Source ROS 2 workspace environment Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/getting-started.md Sets up the environment variables required for Slicer to locate ROS 2 resources. ```bash source ~/ros2_ws/install/setup.bash # or whatever your ROS 2 workspace is ``` -------------------------------- ### Create a Robot Node Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/robots.md Use these commands to programmatically add a robot node to the scene. The process automatically triggers URDF parsing and visual model creation. ```python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() rosNode.CreateAndAddRobotNode('PSM','PSM1/robot_state_publisher','robot_description') # Using the PSM as an example ``` ```cpp auto robot = rosNode->CreateAndAddRobotNode("PSM","PSM1/robot_state_publisher","robot_description"); ``` -------------------------------- ### Load and Visualize Robot in Python Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Load and visualize URDF-defined robots in Slicer. The robot description is retrieved via ROS parameters, and link positions are updated through tf2 lookups. Ensure the robot state publisher is running before creating the robot node. ```Python # Python - Load and visualize a robot # First start the robot state publisher: # ros2 launch sensable_omni_model omni.launch.py rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # Create a robot node # Arguments: robot_name, parameter_node_name, parameter_name robotNode = rosNode.CreateAndAddRobotNode( 'OmniRobot', # Display name in Slicer '/robot_state_publisher', # ROS node holding the URDF 'robot_description' # Parameter name for URDF ) # For namespaced robots (e.g., dVRK PSM1): # ros2 launch dvrk_model arm.launch arm:=PSM1 generation:=Classic psmRobot = rosNode.CreateAndAddRobotNode( 'PSM1', 'PSM1/robot_state_publisher', # Namespace prefix 'robot_description' ) # Robot with custom fixed frame (reference frame for visualization) # Useful when URDF doesn't specify a standard base frame turtlebotRobot = rosNode.CreateAndAddRobotNode( 'TurtleBot', '/robot_state_publisher', 'robot_description' ) # Set fixed frame if needed (default uses first link) # This is configured through the UI or by modifying the robot node # Remove robot when done rosNode.RemoveAndDeleteRobotNode( 'OmniRobot', '/robot_state_publisher', 'robot_description' ) ``` -------------------------------- ### Create and Use Subscribers Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Receive data from ROS 2 topics and use MRML observers to trigger callbacks or update Slicer nodes. ```python # Python - Create and use subscribers rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # Create a string subscriber subString = rosNode.CreateAndAddSubscriberNode('String', '/external_commands') # In terminal: ros2 topic pub /external_commands std_msgs/String "data: 'test'" # Poll for messages message = subString.GetLastMessage() messageYAML = subString.GetLastMessageYAML() messageCount = subString.GetNumberOfMessages() # Create a callback for automatic updates def onStringReceived(caller=None, event=None): msg = subString.GetLastMessage() print(f"Received: {msg}") observerId = subString.AddObserver('ModifiedEvent', onStringReceived) # Subscribe to pose data and update a Slicer transform subPose = rosNode.CreateAndAddSubscriberNode('Pose', '/tracked_tool_pose') transform = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLinearTransformNode', 'ToolPose') def updateToolTransform(caller=None, event=None): pose = subPose.GetLastMessage() # Returns vtkMatrix4x4 transform.SetMatrixTransformToParent(pose) poseObserverId = subPose.AddObserver('ModifiedEvent', updateToolTransform) # Remove observer and subscriber when done subString.RemoveObserver(observerId) rosNode.RemoveAndDeleteSubscriberNode('/external_commands') ``` -------------------------------- ### Create String Subscriber in C++ Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/topics.md Create a subscriber for string messages on a specified topic using C++. Retrieve the last message received using `GetLastMessage`. ```C++ auto subString = rosNode->CreateAndAddSubscriberNode("String", "/my_string"); // run `ros2 topic pub /my_string` in a terminal to send a string to Slicer auto result = subString->GetLastMessage(); ``` -------------------------------- ### Implement VTK WrenchStamped Class and Conversions Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/design.md Provides the C++ implementation for the VTK WrenchStamped class constructor, destructor, PrintSelf method, and the conversion functions between VTK and ROS 2 WrenchStamped messages. This code is automatically generated. ```c++ #include "vtkGeometryMsgsWrenchStamped.h" #include #include // generate_class vtkStandardNewMacro(vtkGeometryMsgsWrenchStamped); vtkGeometryMsgsWrenchStamped::vtkGeometryMsgsWrenchStamped() { header_ = vtkStdMsgsHeader::New(); wrench_ = vtkDoubleArray::New(); } vtkGeometryMsgsWrenchStamped::~vtkGeometryMsgsWrenchStamped() = default; // generate_print_self_methods_for_class void vtkGeometryMsgsWrenchStamped::PrintSelf(std::ostream& os, vtkIndent indent) { Superclass::PrintSelf(os, indent); os << indent << "Header:" << std::endl; header_->PrintSelf(os, indent.GetNextIndent()); os << indent << "Wrench:" << std::endl; wrench_->PrintSelf(os, indent.GetNextIndent()); } // generate_slicer_to_ros2_methods_for_class void vtkSlicerToROS2(vtkGeometryMsgsWrenchStamped* input, geometry_msgs::msg::WrenchStamped & result, const std::shared_ptr& rosNode) { (void)rosNode; // Suppress unused parameter warning vtkSlicerToROS2(input->GetHeader(), result.header, rosNode); vtkSlicerToROS2(input->GetWrench(), result.wrench, rosNode); } // generate_ros2_to_slicer_methods_for_class void vtkROS2ToSlicer(const geometry_msgs::msg::WrenchStamped& input, vtkSmartPointer result) { vtkSmartPointer header = vtkSmartPointer::New(); vtkROS2ToSlicer(input.header, header); result->SetHeader(header); vtkSmartPointer wrench = vtkSmartPointer::New(); vtkROS2ToSlicer(input.wrench, wrench); result->SetWrench(wrench); } ``` -------------------------------- ### Replace QLatin1String with QString in Slicer Source Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/getting-started.md If compiling on Ubuntu 20.04 with a recent Slicer version, you may need to replace QLatin1String with QString. This command finds and replaces all occurrences in the Slicer source directory. ```bash find . -not -path '*/\.git/*' \( -name '*.cxx*' -o -name '*.h*' \) -exec sed -i 's/QLatin1String/QString/g' '{}' \; ``` -------------------------------- ### Create and Use Publishers Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Publish data from Slicer to ROS 2 topics using built-in types like strings, poses, arrays, and wrench messages. ```python # Python - Create and use publishers rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # Create a string publisher (use short name) pubString = rosNode.CreateAndAddPublisherNode('String', '/my_string') # In terminal: ros2 topic echo /my_string pubString.Publish('Hello from Slicer!') # Create a pose publisher for vtkMatrix4x4 -> geometry_msgs/Pose pubMatrix = rosNode.CreateAndAddPublisherNode('Pose', '/robot_pose') mat = pubMatrix.GetBlankMessage() # Returns a vtkMatrix4x4 mat.SetElement(0, 3, 100.0) # X translation in mm mat.SetElement(1, 3, 50.0) # Y translation in mm mat.SetElement(2, 3, 25.0) # Z translation in mm pubMatrix.Publish(mat) # Create a double array publisher pubArray = rosNode.CreateAndAddPublisherNode('DoubleArray', '/sensor_data') import vtk dataArray = vtk.vtkDoubleArray() dataArray.SetNumberOfValues(5) for i in range(5): dataArray.SetValue(i, i * 1.5) pubArray.Publish(dataArray) # Create a WrenchStamped publisher pubWrench = rosNode.CreateAndAddPublisherNode('WrenchStamped', '/force_torque') wrenchMsg = pubWrench.GetBlankMessage() wrench = wrenchMsg.GetWrench() wrench.SetNumberOfValues(6) # [fx, fy, fz, tx, ty, tz] wrench.SetValue(0, 10.0) # Force X wrench.SetValue(1, 5.0) # Force Y wrench.SetValue(2, 2.0) # Force Z pubWrench.Publish(wrenchMsg) # Remove a publisher when done rosNode.RemoveAndDeletePublisherNode('/my_string') ``` -------------------------------- ### Build Slicer ROS 2 Module with Colcon Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/getting-started.md Build the Slicer ROS 2 module using colcon, providing the path to your Slicer build directory. This command is typically used for the initial build. ```bash cd ~/ros2_ws colcon build --cmake-args -DSlicer_DIR:PATH=/home/your_user_name_here/something_something/Slicer-SuperBuild-Debug/Slicer-build -DCMAKE_BUILD_TYPE=Release ``` -------------------------------- ### Reconfigure Slicer ROS 2 Module with ccmake Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/getting-started.md If the Slicer_DIR is not set correctly, you can use ccmake to reconfigure the build. Set Slicer_DIR to point to your Slicer build directory, then configure and generate. ```bash ccmake ~/ros2_ws/build/slicer_ros2_module ``` -------------------------------- ### Create and Use TF2 Lookups in Python Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Create a tf2 lookup node to retrieve transform data between two frames. This node can be directly used as a transform node in Slicer. Ensure tf2 data exists for the specified frames to avoid errors. Observers can be added for real-time updates. ```Python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # Create a lookup for base_link -> end_effector transform # Note: This will generate errors if no tf2 data exists for these frames lookupNode = rosNode.CreateAndAddTf2LookupNode('base_link', 'end_effector') # Get the transform manually import vtk lookupMat = vtk.vtkMatrix4x4() lookupNode.GetMatrixTransformToParent(lookupMat) print(f"End effector X: {lookupMat.GetElement(0, 3)}") # The lookup node IS a transform node - use it directly in Slicer # Set it as parent of a model to track the robot end effector model = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode', 'EndEffectorMarker') model.SetAndObserveTransformNodeID(lookupNode.GetID()) # Add observer for real-time updates def onTransformUpdated(caller=None, event=None): mat = vtk.vtkMatrix4x4() lookupNode.GetMatrixTransformToParent(mat) x, y, z = mat.GetElement(0, 3), mat.GetElement(1, 3), mat.GetElement(2, 3) print(f"Position: ({x:.2f}, {y:.2f}, {z:.2f})") observerId = lookupNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformUpdated) # Clean up lookupNode.RemoveObserver(observerId) rosNode.RemoveAndDeleteTf2LookupNode('base_link', 'end_effector') ``` -------------------------------- ### Execute SlicerROS2 unit tests Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Run the test suite from the Slicer Python console to verify ROS 2 communication and node functionality. ```python # Run all SlicerROS2 unit tests from the Slicer Python console tests = slicer.util.getModuleLogic('ROS2Tests') tests.run() # Individual test classes available: # - TestTurtlesimNode: Tests ROS node creation/destruction # - TestCreateAndAddPubSub: Tests all publisher/subscriber types # - TestParameterNode: Tests parameter monitoring # - TestTf2BroadcasterAndLookupNode: Tests tf2 operations # - TestServiceClient: Tests service client functionality ``` -------------------------------- ### Clean up build artifacts Source: https://github.com/rosmed/slicer_ros2_module/blob/main/release.md Removes temporary tarballs and directories after the release process is complete. ```sh rm *.tgz rm -rf SlicerROS* ``` -------------------------------- ### Run Slicer ROS2 Unit Tests Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/tests.md Execute the unit tests for the ROS2 module using the module logic. ```python tests = slicer.util.getModuleLogic('ROS2Tests') tests.run() ``` -------------------------------- ### Configure ROS 2 nodes in CMake Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Use the generate_ros2_nodes macro in MRML/CMakeLists.txt to define publishers, subscribers, service clients, and dependencies. ```cmake generate_ros2_nodes( GENERATED_FILES_PREFIX "SLICER_ROS2_GENERATED" PUBLISHERS "geometry_msgs/msg/PoseStamped" "geometry_msgs/msg/WrenchStamped" "sensor_msgs/msg/JointState" "your_package/msg/YourCustomMessage" # Add custom messages here SUBSCRIBERS "geometry_msgs/msg/PoseStamped" "sensor_msgs/msg/JointState" "your_package/msg/YourCustomMessage" SERVICE_CLIENTS "turtlesim/srv/Spawn" "your_package/srv/YourCustomService" DEPENDENCIES "std_msgs/msg/Header" "builtin_interfaces/msg/Time" ) ``` -------------------------------- ### List Registered ROS2 Service Clients Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/services.md Retrieve the list of available service client nodes from the default ROS2 node. ```python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() rosNode.RegisteredROS2ServiceClientNodes() ``` -------------------------------- ### Set Slicer Build Options with CMake Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/getting-started.md When compiling Slicer, ensure you use system OpenSSL and bzip2 libraries by setting these CMake options to ON. This is crucial for avoiding compilation errors with the Slicer ROS 2 module. ```bash cmake . -DSlicer_USE_SYSTEM_OpenSSL=ON -DSlicer_USE_SYSTEM_bzip2=ON -DCMAKE_BUILD_TYPE=Release ``` -------------------------------- ### Configure Slicer ROS Module Logic Source: https://github.com/rosmed/slicer_ros2_module/blob/main/Logic/CMakeLists.txt Use SlicerMacroBuildModuleLogic to define the module logic target, specifying export directives, include directories, source files, and target libraries including ROS 2 packages. ```cmake project(vtkSlicer${MODULE_NAME}ModuleLogic) set(KIT ${PROJECT_NAME}) set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_LOGIC_EXPORT") set(${KIT}_INCLUDE_DIRECTORIES ) set(${KIT}_SRCS vtkSlicer${MODULE_NAME}Logic.cxx vtkSlicer${MODULE_NAME}Logic.h ) set(${KIT}_TARGET_LIBRARIES vtkSlicer${MODULE_NAME}ModuleMRML ) #----------------------------------------------------------------------------- SlicerMacroBuildModuleLogic( NAME ${KIT} EXPORT_DIRECTIVE ${${KIT}_EXPORT_DIRECTIVE} INCLUDE_DIRECTORIES ${${KIT}_INCLUDE_DIRECTORIES} SRCS ${${KIT}_SRCS} TARGET_LIBRARIES ${${KIT}_TARGET_LIBRARIES} ${rclcpp_LIBRARIES} ${sensor_msgs_LIBRARIES} ${tf2_ros_LIBRARIES} ${tf2_msgs_LIBRARIES} ${tf2_LIBRARIES} ${turtlesim_LIBRARIES} ${std_srvs_LIBRARIES} ) ``` -------------------------------- ### Package and strip SlicerROS2 release files Source: https://github.com/rosmed/slicer_ros2_module/blob/main/release.md Extracts the Slicer package, copies ROS2 module files, strips shared libraries, and compresses the final distribution. ```sh tar zxf Slicer-5.6.2-2024-04-05-linux-amd64.tar.gz // version and date will change cp vtkSlicerROS2Module* Slicer-5.6.2-2024-04-05-linux-amd64 // copy .txt files cp lib/Slicer-5.6/qt-loadable-modules/*ROS2* Slicer-5.6.2-2024-04-05-linux-amd64/lib/Slicer-5.6/qt-loadable-modules/ // copy modules // strip and remove some old files find Slicer-5.6.2-2024-04-05-linux-amd64 -name "*.so" -exec strip {} \; find Slicer-5.6.2-2024-04-05-linux-amd64 -name "*.pyc" -exec rm {} \; // rename the directory, tar and compress mv Slicer-5.6.2-2024-04-05-linux-amd64 SlicerROS2-5.6.2-0.9-Ubuntu-20.04-Galactic-amd64 tar cf SlicerROS2-5.6.2-0.9-Ubuntu-20.04-Galactic-amd64.tar SlicerROS2-5.6.2-0.9-Ubuntu-20.04-Galactic-amd64 gzip --best SlicerROS2-5.6.2-0.9-Ubuntu-20.04-Galactic-amd64.tar ``` -------------------------------- ### Create and Use String Subscriber in Python Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/topics.md Create a subscriber for string messages on a specified topic. You can retrieve the last message received or set up a callback function to process new messages. Ensure the ROS2 node is spun for updates. ```Python rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # optional, shows which subscribers are available rosNode.RegisteredROS2SubscriberNodes() subString = rosNode.CreateAndAddSubscriberNode('String', '/my_string') # run `ros2 topic pub /my_string` in a terminal to send a string to Slicer m_string = subString.GetLastMessage() # alternate, get a string with the full message m_string_yaml = subString.GetLastMessageYAML() ``` -------------------------------- ### Create and Use TF2 Broadcasters in Python Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Broadcast coordinate frame transformations from Slicer to the ROS 2 tf2 system. Transforms can be sent manually or by observing Slicer transform nodes. ```python # Python - Create and use tf2 broadcasters rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # Create a broadcaster for parent->child transform broadcaster = rosNode.CreateAndAddTf2BroadcasterNode('world', 'tool_tip') # Broadcast a vtkMatrix4x4 transform import vtk toolPose = vtk.vtkMatrix4x4() toolPose.SetElement(0, 3, 100.0) # X position in mm (converted to meters) toolPose.SetElement(1, 3, 50.0) # Y position toolPose.SetElement(2, 3, 25.0) # Z position broadcaster.Broadcast(toolPose) # Alternative: Observe an existing Slicer transform node # When the transform changes, it automatically broadcasts to tf2 slicerTransform = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLinearTransformNode', 'TrackerToWorld') broadcaster.ObserveTransformNode(slicerTransform) # Now any changes to slicerTransform will be broadcast to tf2 mat = vtk.vtkMatrix4x4() mat.SetElement(0, 3, 200.0) slicerTransform.SetMatrixTransformToParent(mat) # Transform is automatically broadcast! # Clean up rosNode.RemoveAndDeleteTf2BroadcasterNode('world', 'tool_tip') ``` -------------------------------- ### Generate ROS2 Nodes with CMake Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/design.md Use the `generate_ros2_nodes` CMake macro to define ROS message types for publishers, subscribers, and service clients. New nodes become available after recompiling the SlicerROS2 module. Ensure external ROS packages are listed in `SlicerROS2_ROS_DEPENDENCIES` if not already used. ```cmake generate_ros2_nodes( GENERATED_FILES_PREFIX "SLICER_ROS2_GENERATED" PUBLISHERS "geometry_msgs/msg/PoseStamped" "geometry_msgs/msg/TransformStamped" "geometry_msgs/msg/WrenchStamped" "sensor_msgs/msg/Joy" "sensor_msgs/msg/JointState" "geometry_msgs/msg/PoseArray" SUBSCRIBERS "geometry_msgs/msg/PoseStamped" "geometry_msgs/msg/TransformStamped" "geometry_msgs/msg/WrenchStamped" "sensor_msgs/msg/Joy" "sensor_msgs/msg/JointState" "geometry_msgs/msg/PoseArray" SERVICE_CLIENTS "turtlesim/srv/Spawn" DEPENDENCIES "std_msgs/msg/Header" "builtin_interfaces/msg/Time" ) ``` -------------------------------- ### Retrieve the Default ROS Node Source: https://context7.com/rosmed/slicer_ros2_module/llms.txt Access the default ROS node to manage publishers, subscribers, and other ROS communication entities. ```python # Python - Get the default ROS node rosLogic = slicer.util.getModuleLogic('ROS2') rosNode = rosLogic.GetDefaultROS2Node() # List available publisher and subscriber types print(rosNode.RegisteredROS2PublisherNodes()) print(rosNode.RegisteredROS2SubscriberNodes()) print(rosNode.RegisteredROS2ServiceClientNodes()) ``` -------------------------------- ### Add Observer for String Subscriber in Python Source: https://github.com/rosmed/slicer_ros2_module/blob/main/docs/pages/nodes/topics.md Attach an observer to a subscriber node to execute a callback function whenever a new message is received. The callback function can then access the latest message data. ```Python # example callback function: def myCallback(caller=None, event=None): message = subString.GetLastMessage() print("Last message received by subscriber: {}.".format(message)) # add the observer with callback observerId = subString.AddObserver('ModifiedEvent', myCallback) # the last message received will print in the python console # in Slicer when data is published to /my_string ```