Dima Kogan2014-07-25T17:34:00Zhttp://notes.secretsauce.netindex.xmlmrcal 2.4 released!Dima Kogan2024-01-26T02:07:00Z2024-01-26T02:07:00Znotes/2024/01/25_mrcal-24-released.html<p>
<a href="https://mrcal.secretsauce.net/">mrcal</a> 2.4 is out: <a href="https://mrcal.secretsauce.net/news-2.4.html">the release notes</a>. Once again, this is mostly a bug-fix
release en route to the big new features coming in 3.0. The most noteworthy
fixes:
</p>
<ul class="org-ul">
<li>mrcal can be built with clang. Try it out like this: <code>CC=clang CXX=clang++
make</code>. This opens up some portability improvements, such as making it easier
to run on Windows.
</li>
<li>Full dense stereo pipeline in C.
</li>
<li>Tools to support more file formats:
<ul class="org-ul">
<li><a href="mrcal-from-kalibr.html"><code>mrcal-from-kalibr</code></a>
</li>
<li><a href="mrcal-to-kalibr.html"><code>mrcal-to-kalibr</code></a>
</li>
<li><a href="mrcal-from-ros.html"><code>mrcal-from-ros</code></a>
</li>
</ul>
<p>
These are experimental. <i>Please</i> let me know if these are or aren't useful
</p>
</li>
</ul>
<p>
The portability work was motivated by <a href="https://github.com/mcm001">Matt Morley</a>, who was interested in
integrating mrcal into <a href="https://photonvision.org/">PhotonVision</a>, the toolkit used by students in the <a href="https://www.firstinspires.org/robotics/frc">FIRST
Robotics Competition</a>. Matt completed that work, and mrcal is now a part of
<a href="https://www.chiefdelphi.com/t/photonvision-2024-release/448635">PhotonVision 2024.1.2</a>! Thanks, Matt!
</p>
<p>
I don't know if there will be a mrcal 2.5, but the next <i>interesting</i> release
will be mrcal 3.0. The biggest internal rework is complete: <a href="https://mrcal.secretsauce.net/docs-3.0/uncertainty-cross-reprojection.html">the new
cross-reprojection uncertainty quantification method is implemented, tested and
documented</a>. The results are <i>very</i> promising, but lots needs to happen before
we can reliably compute intrinsics without chessboards and produce full SFM
solves in mrcal and all the related things.
</p>
roslaunch and =LD_PRELOAD=Dima Kogan2023-12-07T12:56:00Z2023-12-07T12:56:00Znotes/2023/12/07_roslaunch-and-ldpreload.html<p>
This is part 2 of our series entitled "ROS people don't know how to use
computers". This is about ROS1. ROS2 is presumably broken in some completely
different way, but I don't know.
</p>
<p>
Unlike normal people, the ROS people don't "run" applications. They "launch"
"nodes" from "packages" (these are "ROS" packages; obviously). You run
</p>
<div class="org-src-container">
<pre class="src src-sh">roslaunch PACKAGE THING.launch
</pre>
</div>
<p>
Then it tries to find this <code>PACKAGE</code> (using some rules that nobody understands),
and tries to find the file <code>THING.launch</code> within this package. The <code>.launch</code>
file contains inscrutable xml, which includes other inscrutable xml. And
if you dig, you eventually find stuff like
</p>
<div class="org-src-container">
<pre class="src src-xml"><<span style="color: #0000ee; font-weight: bold;">node</span> <span style="color: #cdcd00;">pkg</span>=<span style="color: #00cd00;">"</span><span style="color: #00cd00;">PACKAGE</span><span style="color: #00cd00;">"</span>
<span style="color: #cdcd00;">name</span>=<span style="color: #00cd00;">"</span><span style="color: #00cd00;">NAME</span><span style="color: #00cd00;">"</span>
<span style="color: #cdcd00;">type</span>=<span style="color: #00cd00;">"</span><span style="color: #00cd00;">TYPE</span><span style="color: #00cd00;">"</span>
<span style="color: #cdcd00;">args</span>=<span style="color: #00cd00;">"</span><span style="color: #00cd00;">....</span><span style="color: #00cd00;">"</span>
...>
</pre>
</div>
<p>
This defines the thing that runs. Unexpectedly, the executable that ends up
running is called <code>TYPE</code>.
</p>
<p>
I know that my particular program is broken, and needs an <code>LD_PRELOAD</code> (exciting
details described in another rant in the near future). But the above definition
doesn't have a clear way to add that. Adding it to the <code>type</code> fails (with a very
mysterious error message). Reading <a href="http://wiki.ros.org/roslaunch/XML/node">the docs</a> tells you about <code>launch-prefix</code>,
which sounds exactly like what I want. But when I add
<code>LD_PRELOAD=/tmp/whatever.so</code> I get
</p>
<pre class="example">
RLException: Roslaunch got a 'No such file or directory' error while attempting to run:
LD_PRELOAD=/tmp/whatever.so ..../TYPE .....
</pre>
<p>
But this is how you're supposed to be <a href="http://wiki.ros.org/roslaunch/Tutorials/Roslaunch%20Nodes%20in%20Valgrind%20or%20GDB">attaching gdb and such</a>! Presumably it
looks at the first token, and makes sure it's a file, instead of simply
prepending it to the string it passes to the shell. So your options are:
</p>
<ul class="org-ul">
<li>Do only approved ROS things in the docs (which are limited, since the docs
were written by people who don't know how to use computers)
</li>
<li>Be expert-enough to work around it
</li>
</ul>
<p>
I'm expert-enough. You do this:
</p>
<pre class="example">
launch-prefix="/lib64/ld-linux-x86-64.so.2 --preload /tmp/whatever.so"
</pre>
Talking to ROS from outside a LANDima Kogan2023-10-26T23:25:00Z2023-10-26T23:25:00Znotes/2023/10/26_talking-to-ros-from-outside-a-lan.html
<div id="outline-container-sec-1" class="outline-2">
<h2 id="sec-1">The problem</h2>
<div class="outline-text-2" id="text-1">
<p>
This is about <a href="https://www.ros.org/">ROS</a> version 1. Version 2 is different, and maybe they fixed stuff.
But I kinda doubt it since this thing is heinous in a million ways.
</p>
<p>
Alright so let's say we have have some machines in a LAN doing ROS stuff and we
have another machine outside the LAN that wants to listen in (like to get a
realtime visualization, say). This is an extremely common scenario, but they
created enough hoops to make this not work. Let's say we have 3 computers:
</p>
<ul class="org-ul">
<li><code>router</code>: the bridge between the two networks. This has two NICs. The inner IP
is 10.0.1.1 and the outer IP is 12.34.56.78
</li>
<li><code>inner</code>: a machine in the LAN that's doing ROS stuff. IP 10.0.1.99
</li>
<li><code>outer</code>: a machine outside that LAN that wants to listen in. IP 12.34.56.99
</li>
</ul>
<p>
Let's say the <code>router</code> is doing ROS stuff. It's running the ROS master and some
nodes like this:
</p>
<div class="org-src-container">
<pre class="src src-sh"><span style="color: #cdcd00;">ROS_IP</span>=10.0.1.1 roslaunch whatever
</pre>
</div>
<p>
If you omit the <code>ROS_IP</code> it'll pick <code>router</code>, which may or may not work,
depending on how the DNS is set up. Here we set it to 10.0.1.1 to make it
possible for the <code>inner</code> machine to communicate (we'll see why in a bit). An
aside: ROS should use the IP by default instead of the name because the IP will
work even if the DNS isn't set up. If there are multiple extant IPs, it should
throw an error. But all that would be way too user-friendly.
</p>
<p>
OK. So we have a ROS master on 10.0.1.1 on the default port: 11311. The <code>inner</code>
machine can <code>rostopic echo</code> and all that. Great.
</p>
<p>
What if I try to listen in from <code>outer</code>? I say
</p>
<div class="org-src-container">
<pre class="src src-sh"><span style="color: #cdcd00;">ROS_MASTER_URI</span>=http://12.34.56.78:11311 rostopic list
</pre>
</div>
<p>
This connects to the <code>router</code> on that port, and it works well: I get the list of
available topics. Here this works because the <code>router</code> is the router. If <code>inner</code>
was running the ROS master then we'd need to do a forward for port 11311. In any
case, this works and we understand it.
</p>
<p>
So clearly we can talk to the ROS master. Right? Wrong! Let's actually listen in
on a specific topic on <code>outer</code>:
</p>
<div class="org-src-container">
<pre class="src src-sh"><span style="color: #cdcd00;">ROS_MASTER_URI</span>=http://12.34.56.78:11311 rostopic echo /some/topic
</pre>
</div>
<p>
This does <i>not</i> work. No errors are reported. It just sits there, which looks
like no data is coming in on that topic. But this is a lie: it's actually
broken.
</p>
</div>
</div>
<div id="outline-container-sec-2" class="outline-2">
<h2 id="sec-2">The diagnosis</h2>
<div class="outline-text-2" id="text-2">
<p>
So this is our problem. It's a very common use case, and there are plenty of
internet people asking about it, with no specific solutions. I debugged it, and
the details are here.
</p>
<p>
To figure out what's going on, I made a syscall log on a machine inside the LAN,
where a simple <code>rostopic echo</code> <b>does work</b>:
</p>
<div class="org-src-container">
<pre class="src src-sh">sysdig -A proc.name=rostopic and fd.type contains ipv -s 2000
</pre>
</div>
<p>
This shows us all the communication between <code>inner</code> running <code>rostopic</code> and the
server. It's <i>really</i> chatty. It's all TCP. There are multiple connections to
the <code>router</code> on port 11311. It also starts up multiple TCP servers on the client
that listen to connections; these are likely to be broken if we were running the
client on <code>outer</code> and a machine inside the LAN tried to talk to them; but
thankfully in my limited testing nothing actually tried to talk to them. The
conversations on port 11311 are really long, but here's the punchline.
</p>
<p>
<code>inner</code> tells the <code>router</code>:
</p>
<pre class="example">
POST /RPC2 HTTP/1.1
Host: 10.0.1.1:11311
Accept-Encoding: gzip
Content-Type: text/xml
User-Agent: Python-xmlrpc/3.11
Content-Length: 390
<?xml version='1.0'?>
<methodCall>
<methodName>registerSubscriber</methodName>
<params>
<param>
<value><string>/rostopic_2447878_1698362157834</string></value>
</param>
<param>
<value><string>/some/topic</string></value>
</param>
<param>
<value><string>*</string></value>
</param>
<param>
<value><string>http://inner:38229/</string></value>
</param>
</params>
</methodCall>
</pre>
<p>
Yes. It's laughably chatty. Then the <code>router</code> replies:
</p>
<pre class="example">
HTTP/1.1 200 OK
Server: BaseHTTP/0.6 Python/3.8.10
Date: Thu, 26 Oct 2023 23:15:28 GMT
Content-type: text/xml
Content-length: 342
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><array><data>
<value><int>1</int></value>
<value><string>Subscribed to [/some/topic]</string></value>
<value><array><data>
<value><string>http://10.0.1.1:45517/</string></value>
</data></array></value>
</data></array></value>
</param>
</params>
</methodResponse>
</pre>
<p>
Then this sequence of system calls happens in the <code>rostopic</code> process (an excerpt
from the <code>sysdig</code> log):
</p>
<pre class="example">
> connect fd=10(<4>) addr=10.0.1.1:45517
< connect res=-115(EINPROGRESS) tuple=10.0.1.99:47428->10.0.1.1:45517 fd=10(<4t>10.0.1.99:47428->10.0.1.1:45517)
< getsockopt res=0 fd=10(<4t>10.0.1.99:47428->10.0.1.1:45517) level=1(SOL_SOCKET) optname=4(SO_ERROR) val=0 optlen=4
</pre>
<p>
So the <code>inner</code> client makes an outgoing TCP connection on the address given to
it by the ROS master above: <code>10.0.1.1:45517</code>. This IP is only accessible from
within the LAN, which works fine when talking to it from <code>inner</code>, but would be a
problem from the outside. Furthermore, some sort of single-port-forwarding
scheme wouldn't fix connecting from <code>outer</code> either, since the port number is
dynamic.
</p>
<p>
To confirm what we think is happening, the sequence of syscalls when trying to
<code>rostopic echo</code> from <code>outer</code> does indeed fail:
</p>
<pre class="example">
connect fd=10(<4>) addr=10.0.1.1:45517
connect res=-115(EINPROGRESS) tuple=10.0.1.1:46204->10.0.1.1:45517 fd=10(<4t>10.0.1.1:46204->10.0.1.1:45517)
getsockopt res=0 fd=10(<4t>10.0.1.1:46204->10.0.1.1:45517) level=1(SOL_SOCKET) optname=4(SO_ERROR) val=-111(ECONNREFUSED) optlen=4
</pre>
<p>
That's the breakage mechanism: the ROS master asks us to communicate on an
address we can't talk to.
</p>
<p>
Debugging this is easy with <code>sysdig</code>:
</p>
<div class="org-src-container">
<pre class="src src-sh">sudo sysdig -A -s 400 evt.buffer contains <span style="color: #00cd00;">'"Subscribed to"'</span> and proc.name=rostopic
</pre>
</div>
<p>
This prints out all syscalls seen by the <code>rostopic</code> command that contain the
string <code>Subscribed to</code>, so you can see that different addresses the ROS master
gives us in response to different commands.
</p>
<p>
OK. So can we get the ROS master to give us an address that we can actually talk
to? Sorta. Remember that we invoked the master with
</p>
<div class="org-src-container">
<pre class="src src-sh"><span style="color: #cdcd00;">ROS_IP</span>=10.0.1.1 roslaunch whatever
</pre>
</div>
<p>
The <code>ROS_IP</code> environment variable is exactly the address that the master gives
out. So in this case, we can fix it by doing this instead:
</p>
<div class="org-src-container">
<pre class="src src-sh"><span style="color: #cdcd00;">ROS_IP</span>=12.34.56.78 roslaunch whatever
</pre>
</div>
<p>
Then the <code>outer</code> machine will be asked to talk to 12.34.56.78:45517, which
works. Unfortunately, if we do that, then the <code>inner</code> machine won't be able to
communicate.
</p>
<p>
So some sort of <code>ssh</code> port forward cannot fix this: we need a lower-level
tunnel, like a VPN or something.
</p>
<p>
And another rant. Here <code>rostopic</code> tried to connect to an unreachable address,
which failed. But <code>rostopic</code> <i>knows</i> the connection failed! It should throw an
error message to the user. Something like this would be wonderful:
</p>
<pre class="example">
ERROR! Tried to connect to 10.0.1.1:45517 ($ROS_IP:dynamicport), but connect() returned ECONNREFUSED
</pre>
<p>
That would be immensely helpful. It would tell the user that something went
wrong (instead of no data being sent), and it would give a strong indication of
the problem and how to fix it. But that would be asking too much.
</p>
</div>
</div>
<div id="outline-container-sec-3" class="outline-2">
<h2 id="sec-3">The solution</h2>
<div class="outline-text-2" id="text-3">
<p>
So we need a VPN-like thing. I just tried <code>sshuttle</code>, and it just works.
</p>
<p>
Start the ROS node in the way that makes connections from within the LAN work:
</p>
<div class="org-src-container">
<pre class="src src-sh"><span style="color: #cdcd00;">ROS_IP</span>=10.0.1.1 roslaunch whatever
</pre>
</div>
<p>
Then on the <code>outer</code> client:
</p>
<div class="org-src-container">
<pre class="src src-sh">sshuttle -r router 10.0.1.0/24
</pre>
</div>
<p>
This connects to the <code>router</code> over ssh and does some hackery to make all
connections from <code>outer</code> to 10.0.1.x transparently route into the LAN. On all
ports. <code>rostopic echo</code> then works. I haven't done any thorough testing, but
hopefully it's reliable and has low overhead; I don't know.
</p>
<p>
I haven't tried it but almost certainly this would work even with the ROS master
running on <code>inner</code>. This would be accomplished like this:
</p>
<ol class="org-ol">
<li>Tell <code>ssh</code> how to connect to <code>inner</code>. Dropping this into <code>~/.ssh/config</code>
should do it:
<pre class="example">
Host inner
HostName 10.0.1.99
ProxyJump router
</pre>
</li>
<li>Do the magic thing:
<div class="org-src-container">
<pre class="src src-sh">sshuttle -r inner 10.0.1.0/24
</pre>
</div>
</li>
</ol>
<p>
I'm sure any other VPN-like thing would work also.
</p>
</div>
</div>
mrcal 2.3 released!Dima Kogan2023-05-05T14:13:00Z2023-05-05T14:13:00Znotes/2023/05/05_mrcal-23-released.html<p>
Today I released <a href="http://mrcal.secretsauce.net/">mrcal</a> 2.3 (the release notes are available <a href="http://mrcal.secretsauce.net/news-2.3.html">here</a>). Once again,
in the code there are lots of useful improvements, but nothing major. The big
update in this release is the <a href="http://mrcal.secretsauce.net/">documentation</a>. Much of it was improved and
extended, especially practical guides in the <a href="http://mrcal.secretsauce.net/how-to-calibrate.html">how-to-calibrate page</a> and the
<a href="http://mrcal.secretsauce.net/recipes.html">recipes</a>.
</p>
<p>
Major updates are imminent. I'm about to merge the <a href="https://github.com/dkogan/mrcal/tree/2022-04--cross-uncertainty">cross-projection uncertainty
branch</a> and the
<a href="https://github.com/dkogan/mrcal/tree/2022-06--triangulated-solve">triangulated-points-in-the-solver
branch</a> to study chessboard-less calibrations and structure from motion.
Neither of these are novel, but mrcal's improved lens models and uncertainty
propagation will hopefully produce better results.
</p>
=numpy.percentile= API updateDima Kogan2023-04-20T09:57:00Z2023-04-20T09:57:00Znotes/2023/04/20_numpypercentile-api-update.html<p>
The numpy devs did a bad thing. Don't be like the numpy devs.
</p>
<p>
The <a href="https://numpy.org/doc/1.24/reference/generated/numpy.percentile.html">current (version 1.24) docs for <code>numpy.percentile</code></a> say this about
the <code>method</code> keyword argument:
</p>
<pre class="example">
Changed in version 1.22.0: This argument was previously called "interpolation" ...
</pre>
<p>
They renamed a keyword argument. So if you had working code that did
</p>
<div class="org-src-container">
<pre class="src src-python">np.percentile( ...., interpolation=xxx, ....)
</pre>
</div>
<p>
then running it in the most recent numpy would throw lots of
Deprecation warnings at you, and presumably eventually it will stop
working completely. This isn't great. The obvious answer is to change
the code to
</p>
<div class="org-src-container">
<pre class="src src-python">np.percentile( ...., method=xxx, ....)
</pre>
</div>
<p>
But then if you run it on a machine with an older numpy install, then
it won't work at all! There isn't a trivial method for users of numpy
to conform to this change without breaking stuff. In other words, the
numpy devs gave their users pointless homework. I just did this
homework with <a href="https://github.com/dkogan/mrcal/commit/5a7d3e05ce3d614e08610207f9e28e1d0f87b6ce">this commit to mrcal</a>. It creates a <code>percentile_compat()</code>
function that figures out which flavor of argument we should use, and
uses it. Here it is:
</p>
<div class="org-src-container">
<pre class="src src-python"><span style="color: #00cdcd; font-weight: bold;">def</span> <span style="color: #0000ee; font-weight: bold;">percentile_compat</span>(*args, **kwargs):
r<span style="color: #00cd00;">'''Wrapper for np.percentile() to handle their API change</span>
<span style="color: #00cd00;">In numpy 1.24 the "interpolation" kwarg was renamed to "method". I need to pass</span>
<span style="color: #00cd00;">the right thing to work with both old and new numpy. This function tries the</span>
<span style="color: #00cd00;">newer method, and if that fails, uses the old one. The test is only done the</span>
<span style="color: #00cd00;">first time.</span>
<span style="color: #00cd00;">It is assumed that this is called with the old 'interpolation' key.</span>
<span style="color: #00cd00;"> '''</span>
<span style="color: #00cdcd; font-weight: bold;">if</span> <span style="color: #00cdcd; font-weight: bold;">not</span> <span style="color: #00cd00;">'interpolation'</span> <span style="color: #00cdcd; font-weight: bold;">in</span> kwargs <span style="color: #00cdcd; font-weight: bold;">or</span> \
percentile_compat.which == <span style="color: #00cd00;">'interpolation'</span>:
<span style="color: #00cdcd; font-weight: bold;">return</span> np.percentile(*args, **kwargs)
<span style="color: #cdcd00;">kwargs_no_interpolation</span> = <span style="color: #0000ee; font-weight: bold;">dict</span>(kwargs)
<span style="color: #00cdcd; font-weight: bold;">del</span> kwargs_no_interpolation[<span style="color: #00cd00;">'interpolation'</span>]
<span style="color: #00cdcd; font-weight: bold;">if</span> percentile_compat.which == <span style="color: #00cd00;">'method'</span>:
<span style="color: #00cdcd; font-weight: bold;">return</span> np.percentile(*args, **kwargs_no_interpolation,
method = kwargs[<span style="color: #00cd00;">'interpolation'</span>])
<span style="color: #cdcd00;"># </span><span style="color: #cdcd00;">Need to detect</span>
<span style="color: #00cdcd; font-weight: bold;">try</span>:
<span style="color: #cdcd00;">result</span> = np.percentile(*args, **kwargs_no_interpolation,
method = kwargs[<span style="color: #00cd00;">'interpolation'</span>])
<span style="color: #cdcd00;">percentile_compat.which</span> = <span style="color: #00cd00;">'method'</span>
<span style="color: #00cdcd; font-weight: bold;">return</span> result
<span style="color: #00cdcd; font-weight: bold;">except</span>:
<span style="color: #cdcd00;">percentile_compat.which</span> = <span style="color: #00cd00;">'interpolation'</span>
<span style="color: #00cdcd; font-weight: bold;">return</span> np.percentile(*args, **kwargs)
<span style="color: #cdcd00;">percentile_compat.which</span> = <span style="color: #cd00cd;">None</span>
</pre>
</div>
<p>
Please take it and use it. I give up all copyright.
</p>
Debian at SCaLE 20xDima Kogan2023-03-13T12:58:00Z2023-03-13T12:58:00Znotes/2023/03/13_debian-at-scale-20x.html<p>
SCaLE 20x just wrapped up. We spent three days running the Debian booth: passing
out stickers, penguin swag, coffee and cookies, and telling everyone that would
listen about about our great OS. As usual, Richard Hecker, Chris McKenzie and I
attended as the "LA Debian contingent". Mathias Gibbens flew in from
Albuquerque, and Ha Lam and Syed Reza stopped by periodically.
</p>
<p>
Chris created extra demand by restricting the supply of plushy penguins. Some
kid was shocked at my old laptop, only to see Mathias pull out an even older
one. And we finished off the conference by listening to Ken Thompson's tale
about his music collection. Good times.
</p>
<p>
The crew:
</p>
<div class="figure">
<p><img src="../../../notes/2023/03/13_debian-at-scale-20x/R0003400.jpg" alt="R0003400.jpg" width="50%" />
</p>
</div>
<div class="figure">
<p><img src="../../../notes/2023/03/13_debian-at-scale-20x/R0003423.jpg" alt="R0003423.jpg" width="50%" />
</p>
</div>
<p>
Looking forward to next year!
</p>
gnuplot output in an FLTK widgetDima Kogan2022-10-17T12:28:00Z2022-10-17T12:28:00Znotes/2022/10/17_gnuplot-output-in-an-fltk-widget.html
<div id="outline-container-sec-1" class="outline-2">
<h2 id="sec-1">Overview</h2>
<div class="outline-text-2" id="text-1">
<p>
I make a lot of plots, and the fragmentation of tools in this space <i>really</i>
bugs me. People writing Python code mostly use <code>matplotlib</code>, R people use
<code>ggplot2</code>. MS people use the internal Excel thing. I've seen people use
<code>gtkdatabox</code> for GTK widgets, <code>rrdtool</code> for logging, <code>qcustomplot</code> for qt. And
so on. This is really unhelpful, and it would benefit <i>everybody</i> if there was a
single solid plotting backend with lots of bindings to different languages and
tools.
</p>
<p>
For my own usage, I've been fighting this quixotic battle, using <a href="http://www.gnuplot.info/"><code>gnuplot</code></a> as
the plotting backend for all my use cases. <code>gnuplot</code> is
</p>
<ul class="org-ul">
<li><i>very</i> mature
</li>
<li>stable
</li>
<li>fast
</li>
<li>powerful
</li>
<li>supported on every (with reason) platform
</li>
<li>supports lots and lots of output backends
</li>
</ul>
<p>
There are some things it can't do, but those can be added, and I haven't felt it
to be limiting in over 20 years of using it.
</p>
<p>
I rarely use it directly, and usually interact with it through one of
</p>
<ul class="org-ul">
<li><a href="https://github.com/dkogan/feedgnuplot/"><code>feedgnuplot</code></a> for console use
</li>
<li><a href="https://github.com/dkogan/gnuplotlib/"><code>gnuplotlib</code></a> for programmatic use from Python
</li>
<li><a href="https://metacpan.org/pod/PDL::Graphics::Gnuplot">PDL::Graphics::Gnuplot</a> for programmatic use from Perl
</li>
</ul>
<p>
I wrote all of these, although the Perl library was taken over by others long
ago.
</p>
<p>
Recently I needed a plotting widget for an <a href="https://www.fltk.org/"><code>FLTK</code></a> program written in Python. It
would be great if there was a C++ class deriving from <code>Fl_Widget</code> that would be
wrapped by <a href="https://pyfltk.sourceforge.io/"><code>pyfltk</code></a>, but there isn't.
</p>
<p>
But it turns out that I already had all the tools to quickly hack together
something that mostly works. This <i>is</i> a not-ready-for-primetime hack, but it
works so well, I'd like to write it up. Hopefully this will be done "properly"
someday.
</p>
</div>
</div>
<div id="outline-container-sec-2" class="outline-2">
<h2 id="sec-2">Approach</h2>
<div class="outline-text-2" id="text-2">
<p>
Alright. So here I'm trying to tie together a Python program, <code>gnuplot</code> output
and an <code>FLTK</code> widget. This is a Python program, I can use <a href="https://github.com/dkogan/gnuplotlib/"><code>gnuplotlib</code></a> to talk
to the <code>gnuplot</code> backend. In a perfect world, <code>gnuplot</code> would ship a backend
interfacing to FLTK. But it doesn't. What it <i>does</i> do is to ship an <a href="http://gnuplot.info/docs_5.5/loc22650.html"><code>x11</code>
backend</a> that makes plots with X11 commands, and it allows these commands to be
directed to an <i>arbitrary</i> X11 window. So we
</p>
<ol class="org-ol">
<li>Make an <code>FLTK</code> widget that simply creates an X11 window, and never actually
draws into it
</li>
<li>Tell <code>gnuplot</code> to plot into this window
</li>
</ol>
</div>
</div>
<div id="outline-container-sec-3" class="outline-2">
<h2 id="sec-3">Demo</h2>
<div class="outline-text-2" id="text-3">
<p>
This is really simple, and works shockingly well. Here's my <code>Fl_gnuplotlib</code>
widget:
</p>
<div class="org-src-container">
<pre class="src src-python"><span style="color: #cdcd00;">#</span><span style="color: #cdcd00;">!/usr/bin/python3</span>
<span style="color: #00cdcd; font-weight: bold;">import</span> sys
<span style="color: #00cdcd; font-weight: bold;">import</span> gnuplotlib <span style="color: #00cdcd; font-weight: bold;">as</span> gp
<span style="color: #00cdcd; font-weight: bold;">import</span> fltk
<span style="color: #00cdcd; font-weight: bold;">class</span> <span style="color: #00cd00;">Fl_Gnuplotlib_Window</span>(fltk.Fl_Window):
<span style="color: #00cdcd; font-weight: bold;">def</span> <span style="color: #0000ee; font-weight: bold;">__init__</span>(<span style="color: #00cdcd; font-weight: bold;">self</span>, x,y,w,h, **plot_options):
<span style="color: #0000ee; font-weight: bold;">super</span>().__init__(x,y,w,h)
<span style="color: #00cdcd; font-weight: bold;">self</span>.end()
<span style="color: #00cdcd; font-weight: bold;">self</span>._plot = <span style="color: #cd00cd;">None</span>
<span style="color: #00cdcd; font-weight: bold;">self</span>._delayed_plot_options = <span style="color: #cd00cd;">None</span>
<span style="color: #00cdcd; font-weight: bold;">self</span>.init_plot(**plot_options)
<span style="color: #00cdcd; font-weight: bold;">def</span> <span style="color: #0000ee; font-weight: bold;">init_plot</span>(<span style="color: #00cdcd; font-weight: bold;">self</span>, **plot_options):
<span style="color: #00cdcd; font-weight: bold;">if</span> <span style="color: #00cd00;">'terminal'</span> <span style="color: #00cdcd; font-weight: bold;">in</span> plot_options:
<span style="color: #00cdcd; font-weight: bold;">raise</span> <span style="color: #00cd00;">Exception</span>(<span style="color: #00cd00;">"Fl_Gnuplotlib_Window needs control of the terminal, but the user asked for a specific 'terminal'"</span>)
<span style="color: #00cdcd; font-weight: bold;">if</span> <span style="color: #00cdcd; font-weight: bold;">self</span>._plot <span style="color: #00cdcd; font-weight: bold;">is</span> <span style="color: #00cdcd; font-weight: bold;">not</span> <span style="color: #cd00cd;">None</span>:
<span style="color: #00cdcd; font-weight: bold;">self</span>._plot = <span style="color: #cd00cd;">None</span>
<span style="color: #00cdcd; font-weight: bold;">self</span>._delayed_plot_options = <span style="color: #cd00cd;">None</span>
<span style="color: #cdcd00;">xid</span> = fltk.fl_xid(<span style="color: #00cdcd; font-weight: bold;">self</span>)
<span style="color: #00cdcd; font-weight: bold;">if</span> xid == 0:
<span style="color: #cdcd00;"># </span><span style="color: #cdcd00;">I don't have an xid (yet?), so I delay the init</span>
<span style="color: #00cdcd; font-weight: bold;">self</span>._delayed_plot_options = plot_options
<span style="color: #00cdcd; font-weight: bold;">return</span>
<span style="color: #cdcd00;"># </span><span style="color: #cdcd00;">will barf if we already have a terminal</span>
gp.add_plot_option(plot_options,
terminal = f<span style="color: #00cd00;">'x11 window "0x{xid:x}"'</span>)
<span style="color: #00cdcd; font-weight: bold;">self</span>._plot = gp.gnuplotlib(**plot_options)
<span style="color: #00cdcd; font-weight: bold;">def</span> <span style="color: #0000ee; font-weight: bold;">plot</span>(<span style="color: #00cdcd; font-weight: bold;">self</span>, *args, **kwargs):
<span style="color: #00cdcd; font-weight: bold;">if</span> <span style="color: #00cdcd; font-weight: bold;">self</span>._plot <span style="color: #00cdcd; font-weight: bold;">is</span> <span style="color: #cd00cd;">None</span>:
<span style="color: #00cdcd; font-weight: bold;">if</span> <span style="color: #00cdcd; font-weight: bold;">self</span>._delayed_plot_options <span style="color: #00cdcd; font-weight: bold;">is</span> <span style="color: #cd00cd;">None</span>:
<span style="color: #00cdcd; font-weight: bold;">raise</span> <span style="color: #00cd00;">Exception</span>(<span style="color: #00cd00;">"plot has not been initialized"</span>)
<span style="color: #00cdcd; font-weight: bold;">self</span>.init_plot(**<span style="color: #00cdcd; font-weight: bold;">self</span>._delayed_plot_options)
<span style="color: #00cdcd; font-weight: bold;">if</span> <span style="color: #00cdcd; font-weight: bold;">self</span>._plot <span style="color: #00cdcd; font-weight: bold;">is</span> <span style="color: #cd00cd;">None</span>:
<span style="color: #00cdcd; font-weight: bold;">raise</span> <span style="color: #00cd00;">Exception</span>(<span style="color: #00cd00;">"plot has not been initialized. Delayed initialization failed"</span>)
<span style="color: #00cdcd; font-weight: bold;">self</span>._plot.plot(*args, **kwargs)
</pre>
</div>
<p>
Clearly it's simply making an <code>Fl_Window</code>, and pointing <code>gnuplotlib</code> at it. And
a sample application that uses this widget:
</p>
<div class="org-src-container">
<pre class="src src-python"><span style="color: #cdcd00;">#</span><span style="color: #cdcd00;">!/usr/bin/python3</span>
<span style="color: #00cdcd; font-weight: bold;">import</span> sys
<span style="color: #00cdcd; font-weight: bold;">import</span> numpy <span style="color: #00cdcd; font-weight: bold;">as</span> np
<span style="color: #00cdcd; font-weight: bold;">import</span> numpysane <span style="color: #00cdcd; font-weight: bold;">as</span> nps
<span style="color: #00cdcd; font-weight: bold;">from</span> fltk <span style="color: #00cdcd; font-weight: bold;">import</span> *
<span style="color: #00cdcd; font-weight: bold;">from</span> Fl_gnuplotlib <span style="color: #00cdcd; font-weight: bold;">import</span> *
<span style="color: #cdcd00;">window</span> = Fl_Window(800, 600, <span style="color: #00cd00;">"plot"</span>)
<span style="color: #cdcd00;">plot</span> = Fl_Gnuplotlib_Window(0, 0, 800,600)
<span style="color: #cdcd00;">iplot</span> = 0
<span style="color: #cdcd00;">plotx</span> = np.arange(1000)
<span style="color: #cdcd00;">ploty</span> = nps.cat(plotx*plotx,
np.sin(plotx/100),
plotx)
<span style="color: #00cdcd; font-weight: bold;">def</span> <span style="color: #0000ee; font-weight: bold;">timer_callback</span>(*args):
<span style="color: #00cdcd; font-weight: bold;">global</span> iplot, plotx, ploty, plot
plot.plot(plotx,
ploty[iplot],
_with = <span style="color: #00cd00;">'lines'</span>)
<span style="color: #cdcd00;">iplot</span> += 1
<span style="color: #00cdcd; font-weight: bold;">if</span> iplot == <span style="color: #0000ee; font-weight: bold;">len</span>(ploty):
<span style="color: #cdcd00;">iplot</span> = 0
Fl.repeat_timeout(1.0, timer_callback)
window.resizable(window)
window.end()
window.show()
Fl.add_timeout(1.0, timer_callback)
Fl.run()
</pre>
</div>
<p>
This is nice and simple. Exactly what a program using a widget to make a plot
(while being oblivious to the details) should look like. It creates a window,
places the one plotting widget into it, and cycles the plot inside it at 1Hz
(cycling between a parabola, a sinusoid and a line). Clearly we could place
other UI elements around it, or add more plots, or whatever.
</p>
<p>
The output looks like this:
</p>
<div class="figure">
<p><img src="../../../notes/2022/10/17_gnuplot-output-in-an-fltk-widget/Fl_gnuplotlib_demo.gif" alt="Fl_gnuplotlib_demo.gif" />
</p>
</div>
<p>
To run you need to <code>apt install python3-numpysane python3-gnuplotlib
python3-fltk</code>. If running an older distro on a non-Debian-based distro, you
should grab those from source.
</p>
</div>
</div>
<div id="outline-container-sec-4" class="outline-2">
<h2 id="sec-4">Discussion</h2>
<div class="outline-text-2" id="text-4">
<p>
This works. But it's a hack. Some issues:
</p>
<ul class="org-ul">
<li>This plotting widget currently can output <i>only</i>. It can make whatever plot we
like, but it cannot accept UI input from the container program in any way
</li>
<li>More than that, when focused it completely replaces the FLTK event logic for
that window. So all keyboard input is swallowed, including the keys to access
FLTK menus, to exit the application, etc, etc.
</li>
<li>This approach requires us to use the <code>x11</code> gnuplot terminal. This works, but
it's no longer the terminal preferred by the <code>gnuplot</code> devs, and it it's
maintained as vigilantly as the others.
</li>
<li>And it has bugs. For instance, asking to plot into a window that doesn't yet
exist, causes it to create a new window. This breaks FLTK applications that
start up and create a plot immediately. Here's a <a href="https://sourceforge.net/p/gnuplot/mailman/gnuplot-beta/thread/87pmgf5zlb.fsf@secretsauce.net/#msg37701230">mailing list thread</a>
discussing these issues.
</li>
</ul>
<p>
So this is a <i>very</i> functional hack, but it's still hack. And it feels like
making this solid will take a <i>lot</i> of work. Maybe. I'll push more on this as I
need it. Stay tuned!
</p>
</div>
</div>
mrcal 2.2 releasedDima Kogan2022-10-04T01:05:00Z2022-10-04T01:05:00Znotes/2022/10/04_mrcal-22-released.html<p>
Today I released <a href="http://mrcal.secretsauce.net/">mrcal</a> 2.2 (the release notes are available <a href="http://mrcal.secretsauce.net/news-2.2.html">here</a>). This release
contains lots of medium-important internal improvements, and is a result of
</p>
<ul class="org-ul">
<li>Heavy dogfooding. I use these tools a lot every day, and many things are
nicer, easier and work better in 2.2 than in 2.1
</li>
<li>Not-yet-completed cool new functionality. Some of the required internal
improvements for the big new features are being released here.
</li>
</ul>
<p>
The biggest single new feature in this release is the interactive graphical tool
for examining dense stereo results: accessed via <code>mrcal-stereo --viz stereo</code>.
</p>
<p>
The next pressing thing is improved documentation. The <i>tour of mrcal</i> is still a
good overview of some of the functionality that makes mrcal unique and far
better than traditional calibration tools. But it doesn't do a good job of
demonstrating how you would actually use mrcal to diagnose and handle common
calibration issues. I need to gather some releasable representative data, and
write docs around that.
</p>
<p>
Then I'm going to start finishing the big new features in the <a href="http://mrcal.secretsauce.net/roadmap.html">roadmap</a> (these are
all already functional, but need polish):
</p>
<ul class="org-ul">
<li>Triangulation functions in the optimization loop for efficient
structure-from-motion
</li>
<li>Support for non-central projection to remove a significant source of bias
present in very wide lenses
</li>
<li>Improved projection uncertainty quantification to resolve accuracy and
performance issues in the <a href="http://mrcal.secretsauce.net/uncertainty.html">current projection uncertainty method</a>
</li>
</ul>
vnlog 1.33 releasedDima Kogan2022-06-28T16:47:00Z2022-06-28T16:47:00Znotes/2022/06/28_vnlog-133-released.html<p>
This is a minor release to the <a href="https://github.com/dkogan/vnlog/">vnlog toolkit</a> that adds a few convenience options
to the <a href="https://github.com/dkogan/vnlog/#manpages"><code>vnl-filter</code> tool</a>. The new options are
</p>
<div id="outline-container-sec-1" class="outline-2">
<h2 id="sec-1"><code>vnl-filter -l</code></h2>
<div class="outline-text-2" id="text-1">
<p>
Prints out the existing columns, and exits. I've been low-level wanting this for
years, but never acutely-enough to actually write it. Today I finally did it.
</p>
</div>
</div>
<div id="outline-container-sec-2" class="outline-2">
<h2 id="sec-2"><code>vnl-filter --sub-abs</code></h2>
<div class="outline-text-2" id="text-2">
<p>
Defines an absolute-value <code>abs()</code> function in the default <code>awk</code> mode. I've been
low-level wanting this for years as well. Previously I'd use <code>--perl</code> just to
get <code>abs()</code>, or I'd explicitly define it: =–sub 'abs(x) {return x>0?x:-x;}'=.
Typing all that out was becoming tiresome, and now I don't need to anymore.
</p>
</div>
</div>
<div id="outline-container-sec-3" class="outline-2">
<h2 id="sec-3"><code>vnl-filter --begin ...</code> and <code>vnl-filter --end ...</code></h2>
<div class="outline-text-2" id="text-3">
<p>
Theses add <code>BEGIN</code> and <code>END</code> clauses. They're useful to, for instance, <code>use</code> a
perl module in <code>BEGIN</code>, or to print out some final output in <code>END</code>. Previously
you'd add these inside the <code>--eval</code> block, but that was awkward because <code>BEGIN</code>
and <code>END</code> would then appear inside the <code>while(<>) { }</code> loop. And there was no
clear was to do it in the normal <code>-p</code> mode (no <code>--eval</code>).
</p>
<p>
Clearly these are all minor, since the toolkit is now mature. It does everything
I want it to, that doesn't require lots of work to implement. The big missing
features that I want would patch the underlying <a href="https://www.gnu.org/software/coreutils/">GNU coreutils</a> instead of vnlog:
</p>
<ul class="org-ul">
<li>The <code>sort</code> tool can select different sorting modes, but <code>join</code> works <i>only</i>
with alphanumeric sorting. <code>join</code> should have similarly selectable sorting
modes. In the vnlog wrappe I can currently do something like <code>vnl-join
--vnl-sort n</code>. This would pre-sort the input alphanumerically, and then
post-sort it numerically. That is slow for big datasets. If <code>join</code> could
handle numerically-sorted data directly, neither the pre- or post-sorts would
be needed
</li>
<li>When joining on a numerical field, <code>join</code> should be able to do some sort of
interpolation when given fields that don't match exactly.
</li>
</ul>
<p>
Both of these probably wouldn't take a ton of work to implement, and I'll look
into it someday.
</p>
</div>
</div>
Ricoh GR IIIx 802.11 reverse engineeringDima Kogan2022-06-16T22:04:00Z2022-06-16T22:04:00Znotes/2022/06/16_ricoh-gr-iiix-80211-reverse-engineering.html<p>
I just got a fancy new camera: Ricoh GR IIIx. It's pretty great, and I strongly
recommend it to anyone that wants a truly pocketable camera with fantastic image
quality and full manual controls. One annoyance is the connectivity. It <i>does</i>
have both Bluetooth and 802.11, but the only official method of using them is
some dinky closed phone app. This is silly. I just did some reverse-engineering,
and I now have a functional shell script to download the last few images via
802.11. This is more convenient than plugging in a wire or pulling out the
memory card. Fortunately, Ricoh didn't bend over backwards to make the reversing
difficult, so to figure it out I didn't even need to download the phone app, and
sniff the traffic.
</p>
<p>
When you turn on the 802.11 on the camera, it says stuff about essid and
password, so clearly the camera runs its own access point. Not ideal, but it's
good-enough. I connected, and ran <code>nmap</code> to find hosts and open ports: only port
80 on 192.168.0.1 is open. Pointing <code>curl</code> at it yields some error, so I need to
figure out the valid endpoints. I downloaded the firmware binary, and tried to
figure out what's in it:
</p>
<pre class="example">
dima@shorty:/tmp$ binwalk fwdc243b.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
3036150 0x2E53F6 Cisco IOS microcode, for "8"
3164652 0x3049EC Certificate in DER format (x509 v3), header length: 4, sequence length: 5412
5472143 0x537F8F Copyright string: "Copyright ("
6128763 0x5D847B PARity archive data - file number 90
10711634 0xA37252 gzip compressed data, maximum compression, from Unix, last modified: 2022-02-15 05:47:23
13959724 0xD5022C MySQL ISAM compressed data file Version 11
24829873 0x17ADFB1 MySQL MISAM compressed data file Version 4
24917663 0x17C369F MySQL MISAM compressed data file Version 4
24918526 0x17C39FE MySQL MISAM compressed data file Version 4
24921612 0x17C460C MySQL MISAM compressed data file Version 4
24948153 0x17CADB9 MySQL MISAM compressed data file Version 4
25221672 0x180DA28 MySQL MISAM compressed data file Version 4
25784158 0x1896F5E Cisco IOS microcode, for "\"
26173589 0x18F6095 MySQL MISAM compressed data file Version 4
28297588 0x1AFC974 MySQL ISAM compressed data file Version 6
28988307 0x1BA5393 MySQL ISAM compressed data file Version 3
28990184 0x1BA5AE8 MySQL MISAM index file Version 3
29118867 0x1BC5193 MySQL MISAM index file Version 3
29449193 0x1C15BE9 JPEG image data, JFIF standard 1.01
29522133 0x1C278D5 JPEG image data, JFIF standard 1.08
29522412 0x1C279EC Copyright string: "Copyright ("
29632931 0x1C429A3 JPEG image data, JFIF standard 1.01
29724094 0x1C58DBE JPEG image data, JFIF standard 1.01
</pre>
<p>
The <code>gzip</code> chunk looks like what I want:
</p>
<pre class="example">
dima@shorty:/tmp$ tail -c+10711635 fwdc243b.bin> /tmp/tst.gz
dima@shorty:/tmp$ < /tmp/tst.gz gunzip | file -
/dev/stdin: ASCII cpio archive (SVR4 with no CRC)
dima@shorty:/tmp$ < /tmp/tst.gz gunzip > tst.cpio
</pre>
<p>
OK, we have some <code>.cpio</code> thing. It's plain-text. I grep around it in, looking
for <code>GET</code> and <code>POST</code> and such, and I see various URI-looking things at
<code>/v1/....</code>. Grepping for that I see
</p>
<pre class="example">
dima@shorty:/tmp$ strings tst.cpio | grep /v1/
GET /v1/debug/revisions
GET /v1/ping
GET /v1/photos
GET /v1/props
PUT /v1/params/device
PUT /v1/params/lens
PUT /v1/params/camera
GET /v1/liveview
GET /v1/transfers
POST /v1/device/finish
POST /v1/device/wlan/finish
POST /v1/lens/focus
POST /v1/camera/shoot
POST /v1/camera/shoot/compose
POST /v1/camera/shoot/cancel
GET /v1/photos/{}/{}
GET /v1/photos/{}/{}/info
PUT /v1/photos/{}/{}/transfer
/v1/photos/<string>/<string>
/v1/photos/<string>/<string>/info
/v1/photos/<string>/<string>/transfer
/v1/device/finish
/v1/device/wlan/finish
/v1/lens/focus
/v1/camera/shoot
/v1/camera/shoot/compose
/v1/camera/shoot/cancel
/v1/changes
/v1/changes message received.
/v1/changes issue event.
/v1/changes new websocket connection.
/v1/changes websocket connection closed. reason({})
/v1/transfers, transferState({}), afterIndex({}), limit({})
</pre>
<p>
Jackpot. I pointed <code>curl</code> at most of these, and they do interesting things.
Generally they all spit out JSON. <code>/v1/liveview</code> sends out a sequence of JPEG
images. The thing I care about is <code>/v1/photos/DIRECTORY/FILE</code> and
<code>/v1/photos/DIRECTORY/FILE/info</code>. The result is a script I just wrote to connect
to the camera, download <code>N</code> images, and connect back to the original access
point:
</p>
<p>
<a href="https://github.com/dkogan/ricoh-download">https://github.com/dkogan/ricoh-download</a>
</p>
<p>
Kinda crude, but works for now. I'll improve it with time.
</p>
<p>
After I did this I found an old thread from 2015 where somebody was using an
apparently-compatible camera, and wrote a fancier tool:
</p>
<p>
<a href="https://www.pentaxforums.com/forums/184-pentax-k-s1-k-s2/295501-k-s2-wifi-laptop-2.html">https://www.pentaxforums.com/forums/184-pentax-k-s1-k-s2/295501-k-s2-wifi-laptop-2.html</a>
</p>