<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Python on Mulham Fetna</title>
    <link>https://mulhamfetna.com/tags/python/</link>
    <description>Recent content in Python on Mulham Fetna</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <managingEditor>contact@mulhamfetna.com (Mulham Fetna)</managingEditor>
    <webMaster>contact@mulhamfetna.com (Mulham Fetna)</webMaster>
    <copyright>© 2026 Mulham Fetna</copyright>
    <lastBuildDate>Wed, 16 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://mulhamfetna.com/tags/python/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Finger joint angles from three hand landmarks: the dot-product geometry</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Every knuckle angle in this project comes from three landmarks and one dot product. No learning,&#xA;no lookup table — just the definition of the angle between two vectors, plus two guards that keep a&#xA;single glitchy frame from sending NaN into a motor command.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Three points make an angle&#xA;    &lt;div id=&#34;three-points-make-an-angle&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#three-points-make-an-angle&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;An angle needs a vertex and two rays. A knuckle is the vertex; the two bones meeting there are the rays:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;a &lt;strong&gt;base point&lt;/strong&gt;, where the previous bone starts,&lt;/li&gt;&#xA;&lt;li&gt;the &lt;strong&gt;vertex&lt;/strong&gt; — the knuckle being measured,&lt;/li&gt;&#xA;&lt;li&gt;an &lt;strong&gt;end point&lt;/strong&gt;, where the next bone ends.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart LR&#xA;    P1((&#34;p1&lt;br&gt;base&#34;)) -- &#34;v1 = p1 − p2&#34; --- P2((&#34;p2&lt;br&gt;vertex&lt;br&gt;knuckle&#34;))&#xA;    P2 -- &#34;v2 = p3 − p2&#34; --- P3((&#34;p3&lt;br&gt;end&#34;))&#xA;&lt;/pre&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The triplet table&#xA;    &lt;div id=&#34;the-triplet-table&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-triplet-table&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;finger_triplets&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;thumb&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;index&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;middle&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;9&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;9&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;ring&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;   &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;pinky&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;17&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;17&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;19&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;19&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;MediaPipe landmark indices used to build the triplets&#34;&#xA;    width=&#34;2146&#34;&#xA;    height=&#34;744&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_788faf393ce06561.png&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_788faf393ce06561.png 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_8e6e6baf8bb39074.png 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks.png&#34;&gt;&lt;figcaption&gt;Landmark 0 — the wrist — starts every finger&amp;rsquo;s first triplet.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Finger&lt;/th&gt;&#xA;          &lt;th&gt;Triplet&lt;/th&gt;&#xA;          &lt;th&gt;Vertex&lt;/th&gt;&#xA;          &lt;th&gt;Joint measured&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(0, 5, 6)&lt;/td&gt;&#xA;          &lt;td&gt;5&lt;/td&gt;&#xA;          &lt;td&gt;MCP — joins finger to palm&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(5, 6, 7)&lt;/td&gt;&#xA;          &lt;td&gt;6&lt;/td&gt;&#xA;          &lt;td&gt;PIP — middle knuckle&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(6, 7, 8)&lt;/td&gt;&#xA;          &lt;td&gt;7&lt;/td&gt;&#xA;          &lt;td&gt;DIP — fingertip knuckle&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(0, 1, 2)&lt;/td&gt;&#xA;          &lt;td&gt;1&lt;/td&gt;&#xA;          &lt;td&gt;CMC — saddle joint at the wrist&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(1, 2, 3)&lt;/td&gt;&#xA;          &lt;td&gt;2&lt;/td&gt;&#xA;          &lt;td&gt;MCP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(2, 3, 4)&lt;/td&gt;&#xA;          &lt;td&gt;3&lt;/td&gt;&#xA;          &lt;td&gt;IP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;strong&gt;Why every first triplet starts at 0.&lt;/strong&gt; The palm has no landmark of its own, so the wrist → knuckle&#xA;line stands in for the metacarpal bone. It isn&amp;rsquo;t exactly collinear with a straight finger — ring and&#xA;pinky metacarpals fan outward — so a relaxed straight finger rarely measures a full \(\pi\). That&amp;rsquo;s&#xA;part of why the calibrated &amp;ldquo;straight&amp;rdquo; threshold is 3.10 rad rather than 3.14.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/cover.png" />
    </item>
    
    <item>
      <title>From finger flexion to tendon force: linear interpolation onto a MuJoCo motor</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-to-force/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-to-force/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  On the far side of the ROS 2 topic, five flexions arrive and five tendon motors wait. One line of&#xA;linear interpolation connects them — plus a name lookup that can fail silently, and a string that is&#xA;allowed to push.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The formula&#xA;    &lt;div id=&#34;the-formula&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-formula&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;$$F(t) = F_\text{open} + t\,(F_\text{closed} - F_\text{open}) = 50 + t\,(-50 - 50) = 50 - 100\,t$$&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;FORCE_OPEN&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;50.0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;FORCE_CLOSED&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;50.0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;_lerp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;end_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;end_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;apply_flexions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flexions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;finger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flexion_amount&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flexions&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;items&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;target_force&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_lerp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FORCE_OPEN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FORCE_CLOSED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flexion_amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctrl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;motors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;finger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;target_force&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;div class=&#34;chart&#34;&gt;&#xA;  &lt;canvas id=&#34;chart-0ef11f389f0a690c0b3dc91c1b080552&#34;&gt;&lt;/canvas&gt;&#xA;  &lt;script type=&#34;text/javascript&#34;&gt;&#xA;    window.addEventListener(&#34;DOMContentLoaded&#34;, (event) =&gt; {&#xA;      const ctx = document.getElementById(&#34;chart-0ef11f389f0a690c0b3dc91c1b080552&#34;);&#xA;      const chart = new Chart(ctx, {&#xA;        &#xA;type: &#39;line&#39;,&#xA;data: {&#xA;  labels: [&#39;0.0&#39;,&#39;0.1&#39;,&#39;0.2&#39;,&#39;0.3&#39;,&#39;0.4&#39;,&#39;0.5&#39;,&#39;0.6&#39;,&#39;0.7&#39;,&#39;0.8&#39;,&#39;0.9&#39;,&#39;1.0&#39;],&#xA;  datasets: [{&#xA;    label: &#39;Tendon motor command (N)&#39;,&#xA;    data: [50, 40, 30, 20, 10, 0, -10, -20, -30, -40, -50],&#xA;    borderColor: &#39;#2a78d6&#39;, backgroundColor: &#39;#2a78d6&#39;, borderWidth: 2, pointRadius: 3, tension: 0&#xA;  }]&#xA;},&#xA;options: {&#xA;  plugins: { legend: { display: false }, title: { display: true, text: &#39;Flexion → force: +50 N open, 0 N at 0.5, −50 N closed&#39; } },&#xA;  scales: { x: { title: { display: true, text: &#39;flexion&#39; } }, y: { title: { display: true, text: &#39;force (N) — negative pulls&#39; } } }&#xA;}&#xA;&#xA;      });&#xA;    });&#xA;  &lt;/script&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Three parts of one line&#xA;    &lt;div id=&#34;three-parts-of-one-line&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#three-parts-of-one-line&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;With \(t = 0.75\), a finger 75% closed:&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-to-force/cover.png" />
    </item>
    
    <item>
      <title>MediaPipe Hands for robotics — and the 16° angle trap in normalized landmarks</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Twenty-one points per frame, on a laptop CPU, from a flat RGB image. That is what MediaPipe hands&#xA;to a robot. It is easy to treat as a black box — until you compute angles from it and discover the&#xA;box stretched your coordinate space.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Where MediaPipe sits&#xA;    &lt;div id=&#34;where-mediapipe-sits&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#where-mediapipe-sits&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart TB&#xA;    CAM[&#34;USB webcam&lt;br&gt;640×480 @ 30 fps · YUYV&#34;] --&gt;|&#34;BGR frame&#34;| RGB[&#34;cv2.cvtColor&lt;br&gt;BGR → RGB&#34;]&#xA;    RGB --&gt; MP[&#34;MediaPipe Hands&lt;br&gt;palm detector + landmark model&#34;]&#xA;    MP --&gt;|&#34;21 × (x, y, z)&#34;| ANG[&#34;Triplet angles&lt;br&gt;Part 5&#34;]&#xA;    ANG --&gt; AVG[&#34;Average per finger&lt;br&gt;Part 6&#34;]&#xA;    AVG --&gt; NORM[&#34;Flexion 0..1&lt;br&gt;Parts 7–8&#34;]&#xA;    NORM --&gt;|&#34;/hand/target_flexions&#34;| TWIN[&#34;MuJoCo twin&#34;]&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The vision layer knows nothing about MuJoCo. Its entire output is &lt;strong&gt;five numbers between 0.0 (open)&#xA;and 1.0 (closed)&lt;/strong&gt;.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Two networks, not one&#xA;    &lt;div id=&#34;two-networks-not-one&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#two-networks-not-one&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;MediaPipe Hands is a &lt;strong&gt;cascade&lt;/strong&gt;:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;BlazePalm&lt;/strong&gt;, a single-shot detector, finds a &lt;em&gt;palm&lt;/em&gt; bounding box in the full frame. Palms, not&#xA;hands: a palm is close to a rigid square; a hand with moving fingers is not.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;A landmark model&lt;/strong&gt; crops that region and regresses 21 keypoints, a hand-presence score and&#xA;handedness.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;In video mode (&lt;code&gt;static_image_mode=False&lt;/code&gt;) the detector barely runs. Landmarks from frame &lt;em&gt;t&lt;/em&gt; define&#xA;the crop for frame &lt;em&gt;t+1&lt;/em&gt;, and the detector wakes only when tracking confidence drops. That shortcut&#xA;is why this pipeline holds 30 fps on a CPU.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/cover.png" />
    </item>
    
    <item>
      <title>Normalizing finger curl: from radians to a 0–1 flexion</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Radians belong to the camera. Newtons belong to the robot. The number that crosses between them is a&#xA;plain percentage — and producing it takes one inverted formula and one clip.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The formula&#xA;    &lt;div id=&#34;the-formula&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-formula&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;RAW_STRAIGHT_ANGLE&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;3.10&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# ~177.6° — open finger&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;RAW_CURLED_ANGLE&lt;/span&gt;   &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1.60&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# ~91.7° — fully curled finger&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;flexion&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;straight_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;avg_angle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;straight_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;curled_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;flexions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;finger_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;clip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;flexion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;0.0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1.0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;$$\text{flexion} = \operatorname{clip}\!\left(\frac{\theta_\text{straight} - \bar\theta}{\theta_\text{straight} - \theta_\text{curled}},\; 0,\; 1\right)$$&#xA;&lt;div class=&#34;chart&#34;&gt;&#xA;  &lt;canvas id=&#34;chart-0927e0044df1c84967fec69d742e1c22&#34;&gt;&lt;/canvas&gt;&#xA;  &lt;script type=&#34;text/javascript&#34;&gt;&#xA;    window.addEventListener(&#34;DOMContentLoaded&#34;, (event) =&gt; {&#xA;      const ctx = document.getElementById(&#34;chart-0927e0044df1c84967fec69d742e1c22&#34;);&#xA;      const chart = new Chart(ctx, {&#xA;        &#xA;type: &#39;line&#39;,&#xA;data: {&#xA;  labels: [&#39;3.30&#39;,&#39;3.20&#39;,&#39;3.10&#39;,&#39;3.00&#39;,&#39;2.90&#39;,&#39;2.80&#39;,&#39;2.70&#39;,&#39;2.60&#39;,&#39;2.50&#39;,&#39;2.40&#39;,&#39;2.30&#39;,&#39;2.20&#39;,&#39;2.10&#39;,&#39;2.00&#39;,&#39;1.90&#39;,&#39;1.80&#39;,&#39;1.70&#39;,&#39;1.60&#39;,&#39;1.50&#39;,&#39;1.40&#39;,&#39;1.30&#39;],&#xA;  datasets: [&#xA;    { label: &#39;Index–pinky window (3.10 → 1.60 rad)&#39;, data: [0,0,0,0.067,0.133,0.2,0.267,0.333,0.4,0.467,0.533,0.6,0.667,0.733,0.8,0.867,0.933,1,1,1,1], borderColor: &#39;#2a78d6&#39;, backgroundColor: &#39;#2a78d6&#39;, borderWidth: 2, pointRadius: 2, tension: 0 },&#xA;    { label: &#39;Thumb window (2.90 → 2.30 rad)&#39;, data: [0,0,0,0,0,0.167,0.333,0.5,0.667,0.833,1,1,1,1,1,1,1,1,1,1,1], borderColor: &#39;#eb6834&#39;, backgroundColor: &#39;#eb6834&#39;, borderWidth: 2, pointRadius: 2, tension: 0 }&#xA;  ]&#xA;},&#xA;options: {&#xA;  plugins: { title: { display: true, text: &#39;Average knuckle angle → normalized flexion&#39; } },&#xA;  scales: {&#xA;    x: { title: { display: true, text: &#39;average knuckle angle (rad) — straight → curled&#39; } },&#xA;    y: { min: 0, max: 1.05, title: { display: true, text: &#39;flexion (0 open · 1 closed)&#39; } }&#xA;  }&#xA;}&#xA;&#xA;      });&#xA;    });&#xA;  &lt;/script&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The three pieces&#xA;    &lt;div id=&#34;the-three-pieces&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-three-pieces&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Denominator — the range.&lt;/strong&gt; \(3.10 - 1.60 = 1.50\) rad of travel. It sets the scale.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/cover.png" />
    </item>
    
    <item>
      <title>The ROS 2 topic contract: one JointState topic, two nodes, and rclpy inside a render loop</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The whole ROS 2 layer is one topic carrying five numbers. The interesting decisions are what those&#xA;numbers mean, which message type carries them, and how to run a ROS subscriber when a 3D viewer owns&#xA;your main thread.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Why ROS 2 between vision and physics at all?&#xA;    &lt;div id=&#34;why-ros-2-between-vision-and-physics-at-all&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#why-ros-2-between-vision-and-physics-at-all&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The single-process script works well. Splitting it across ROS 2 buys:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Benefit&lt;/th&gt;&#xA;          &lt;th&gt;Concretely&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Process isolation&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;a MediaPipe crash doesn&amp;rsquo;t kill the simulator, and vice versa&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Independent environments&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;vision and physics get their own container, dependencies and restarts&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Swappable endpoints&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;replace the twin with a servo driver, or the tracker with a data glove&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Free observability&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ros2 topic echo&lt;/code&gt;, &lt;code&gt;hz&lt;/code&gt;, &lt;code&gt;bag record&lt;/code&gt; on the live stream&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Network transparency&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;any machine on the LAN can subscribe&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;The costs — a bigger stack, DDS configuration, one more hop — are small next to 19–85 ms of inference&#xA;(&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/latency-benchmark/&#34; &gt;Part 19&lt;/a&gt;).&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The node graph&#xA;    &lt;div id=&#34;the-node-graph&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-node-graph&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart LR&#xA;    subgraph C1[&#34;🐳 vision_tracker&#34;]&#xA;        V[&#34;/vision_tracker_node&lt;br&gt;timer · 30 Hz&#34;]&#xA;    end&#xA;    subgraph C2[&#34;🐳 mujoco_twin&#34;]&#xA;        T[&#34;/mujoco_twin_node&lt;br&gt;spin_once in viewer loop&#34;]&#xA;    end&#xA;    V -- &#34;/hand/target_flexions&lt;br&gt;sensor_msgs/JointState · depth 10&#34; --&gt; T&#xA;    V -. &#34;any LAN subscriber&lt;br&gt;ROS_DOMAIN_ID=42&#34; .-&gt; X[&#34;ros2 topic echo · rosbag&lt;br&gt;future servo driver&#34;]&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;Peace sign live: the tracked hand, the twin, and the forces produced from the topic&amp;rsquo;s flexions&#34;&#xA;    width=&#34;1570&#34;&#xA;    height=&#34;480&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/live_peace_sign_hu_10d6cdce7106932.jpg&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/live_peace_sign_hu_10d6cdce7106932.jpg 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/live_peace_sign_hu_c82c14c5f3244f84.jpg 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/live_peace_sign.jpg&#34;&gt;&lt;figcaption&gt;Every frame of the twin is driven by one JointState message like the one below.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The contract&#xA;    &lt;div id=&#34;the-contract&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-contract&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Field&lt;/th&gt;&#xA;          &lt;th&gt;Value&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Topic&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/hand/target_flexions&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Type&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;sensor_msgs/msg/JointState&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Publisher&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;vision_tracker_node&lt;/code&gt;, timer at 30 Hz — effective rate bounded by inference&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Subscriber&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;mujoco_twin_node&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;QoS&lt;/td&gt;&#xA;          &lt;td&gt;default reliable, keep-last &lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;header.stamp&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;publisher clock at publish time&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;[&amp;quot;thumb&amp;quot;, &amp;quot;index&amp;quot;, &amp;quot;middle&amp;quot;, &amp;quot;ring&amp;quot;, &amp;quot;pinky&amp;quot;]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;position&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;flexion per finger, &lt;strong&gt;0.0 open … 1.0 closed&lt;/strong&gt;, same order as &lt;code&gt;name&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;velocity&lt;/code&gt;, &lt;code&gt;effort&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;empty&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;A real message, captured with &lt;code&gt;ros2 topic echo&lt;/code&gt; during testing (published by hand with &lt;code&gt;ros2 topic pub&lt;/code&gt;,&#xA;hence the zero stamp):&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/cover.png" />
    </item>
    
  </channel>
</rss>
