
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Mike Tyka, PhD</title>
    <description>Climate Research, AI &amp; Media Art, Sculpture, Machine Learning, SciArt, Biochemistry, Glass Casting
</description>
    <link>http://mtyka.github.io//</link>
    <atom:link href="http://mtyka.github.io//feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 19 Oct 2024 17:58:54 +0000</pubDate>
    <lastBuildDate>Sat, 19 Oct 2024 17:58:54 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Calcium Carbonate to store CO₂ ? </title>
        <description>&lt;p&gt;The earth’s oceans contain a great deal of dissolved inorganic carbon (DIC) and a decent concentration of calcium (about 10mM).
Furthermore the ocean has a huge surface area and is able to take up large quantities of CO₂ from the atmosphere.
It is thus sometimes suggested that inducing precipitation of calcium carbonate (CaCO₃) from seawater could be a viable method to 
lock down carbon into a solid form, which could then be safely and easily stored somewhere. The thought is that removing carbonate from the 
ocean would subsequently cause it to absorb more CO₂ from the atmosphere.&lt;/p&gt;

&lt;p&gt;Unintuitively, it turns out that this is not the case due to the way in which the ocean carbon chemistry works. 
Paradoxically, the exact opposite is true - precipitation of CaCO₃ in the ocean &lt;strong&gt;releases&lt;/strong&gt; CO₂ to the atmosphere - how can this be ? 
This fallacy appears frequently enough, surprisingly sometimes even in peer reviewed papers &lt;a href=&quot;https://chemistry-europe.onlinelibrary.wiley.com/doi/10.1002/cssc.202100134&quot;&gt;¹&lt;/a&gt;ᐧ &lt;a href=&quot;https://pubs.acs.org/doi/full/10.1021/acssuschemeng.0c08561&quot;&gt;²&lt;/a&gt; &lt;a href=&quot;https://newscenter.lbl.gov/2022/05/16/using-bacteria-to-accelerate-co2-capture-in-oceans/&quot;&gt;⁴&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or startups&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.fastcompany.com/90642340/this-carbon-capture-tech-removes-co2-from-the-ocean-by-making-seashells&quot;&gt;³&lt;/a&gt;, that it bears illuminating.&lt;/p&gt;

&lt;h2 id=&quot;inorganic-carbon-in-the-ocean&quot;&gt;Inorganic carbon in the ocean&lt;/h2&gt;

&lt;p&gt;Most gases like O₂ or N₂ are not all that soluble in water because water is a polar liquid and these small molecules are non-polar.
At first glance CO₂ might be thought to behave similarly, after all it’s not particularly polar.
However, when CO₂ dissolves in water it can react with a hydroxyl ion to form a bicarbonate ion.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CO₂ + OH⁻  → HCO₃⁻ 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The bicarbonate ion can further react with another hydroxyl ion to form a carbonate ion.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HCO₃⁻ + OH⁻  → CO₃²⁻ 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;sup&gt;(These reactions are often written as between CO₂ and water, releasing protons, but for 
illustration purposes i think it’s more instructive to think of it the above way.)
&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The key is that unlike CO₂ these carbonate ions are charged and tremendously soluble in water.&lt;/p&gt;

&lt;p&gt;In pure water the availability of hydroxyl ions is very small, available only from the small amount of water that is always dissociated 
into H⁺ and OH⁻. Any CO₂ that dissolves from the air quickly reacts with the available OH⁻ and the subsequent imbalance of H⁺ and OH⁻ results in the solution to become acidic. 
This makes the availablity of OH⁻ even smaller and opposes further reaction.
Thus the solubility of CO₂ in pure water is not much greater than that of other simple gases like O₂ or N₂, which do not react with water.
How could we increase the ability of water to absorb CO₂ ? Simple: supply more OH⁻ ions that can gobble up CO₂ and 
turn it into bicarbonate, in other words, make the water more &lt;em&gt;alkaline&lt;/em&gt;. This excess quantity of hydroxyl ions, which can react with CO₂, 
is called the water’s “alkalinity”. Thus the most basic way to express a quantifiable alkalinity is&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Alk = [HCO₃⁻] + 2×[CO₃²⁻] + [OH⁻] + ... 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This reflects both the excess OH⁻ that has already reacted with CO₂ into bicarbonate/carbonate ions (first two terms) and any free OH⁻ that is still available to react (third term).
This quantity effectively respresents the capacity of a body of water to absorb CO₂, irrespective of how much CO₂ it has currently taken up.
&lt;a href=&quot;https://en.wikipedia.org/wiki/Alkalinity&quot;&gt;Alkalinity&lt;/a&gt; is a complicated concept and a full expression includes a number of other weak bases, 
but the carbonate ions are typically the most important and this simplified view is sufficient to understand carbonate precipitation.&lt;/p&gt;

&lt;p&gt;Seawater contains a large quantity of alkalinity, which originates from dissolving alkaline rocks, which flows into the sea with the rivers.
This alkalinity allows seawater to hold a huge quantity of CO₂; in fact the total quantity of CO₂ dissolved in the ocean (37000GtC) is 
45 times greater than in the atmosphere (840GtC). Most of it is, of course, in the form of bicarbonate (89%)  and carbonate ions (10%).
Only a tiny fraction (0.5%) is dissolved as actual CO₂, like an ordinary gas.&lt;/p&gt;

&lt;h2 id=&quot;precipitating-carbonates&quot;&gt;Precipitating carbonates&lt;/h2&gt;

&lt;p&gt;To understand why precipitating carbonates lead to the release of CO₂ into the atmosphere, let’s look at the precipitation of CaCO₃. 
The reaction can be written in a number of ways, but they all result in the same outcome:
Lets try one line of argument: Since most of the available carbonates are in the bicarbonate form (HCO₃⁻) one way to write the precipitation is :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; Ca²⁺ + 2HCO₃⁻ → CaCO₃ + CO₂ + H₂O
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can immediately see that for every CaCO₃ created, one CO₂ is moved from the carbonate pool (highly soluble) to the gas form (poorly soluble),
and is thus released back into the atmosphere.&lt;/p&gt;

&lt;p&gt;Another way to think about it is to ask “what would we need to do to restore the ocean to its prior state after precipitating a CaCO₃ ?”
First, we precipitate the CaCO₃:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; Ca²⁺ + CO₃²⁻ → CaCO₃ 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, let’s restore the used-up CO₃²⁻ by bringing in a CO₂ from the atmosphere:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; CO₂ + 2OH⁻ → CO₃²⁻ + H₂O 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can see that doing so requires us to add two OH⁻ back to the ocean, in addition to the CO₂. (We won’t worry here about changes in the Ca²⁺ concentration). 
In other words a process that removes calcium carbonate from the ocean in a pH-neutral way needs to replenish the 
alkalinity from somewhere. If not, we will decrease the oceans capacity to store CO₂ by &lt;em&gt;two&lt;/em&gt; units (we used up two OH⁻), for every &lt;em&gt;one&lt;/em&gt; unit carbon we’ve precipitated.
Since we’ve only removed one mol of carbon from the ocean, another unit of carbon will be released to the atmosphere.&lt;/p&gt;

&lt;h2 id=&quot;making-alkalinity&quot;&gt;Making alkalinity&lt;/h2&gt;

&lt;p&gt;Ok, but what if we generated new OH⁻ (i.e. alkalinity) in some way (for example using electrochemistry, like 
the &lt;a href=&quot;https://en.wikipedia.org/wiki/Chloralkali_process&quot;&gt;Chlor Alkali&lt;/a&gt; process or by mining alkaline minerals) - could we use those to precipitate CaCO₃ and lock down CO₂ ?
Yes, absolutely that would work. For every two moles of OH⁻ you could lock down one mol of carbon:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; Ca²⁺ + CO₂ + 2OH⁻ → CaCO₃ + H₂O
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But, wait! There’s an even better thing you could do. You could simply add the alkalinity to the ocean and &lt;em&gt;not&lt;/em&gt; precipitate any calcium carbonate. As we discussed earlier that would
raise the oceans capacity to absorb CO₂ by two moles! Now you’re getting twice the bang for your buck: every mol of OH⁻ pulls down one mol of carbon! (actually the true number is a little lower, about 0.85 or so, for complicated reasons we won’t go into here).
Why ? Because in the ocean most of the carbon is stored as bicarbonate instead of carbonate, and to convert a CO₂ to HCO₃⁻ you only need &lt;em&gt;one&lt;/em&gt; OH⁻:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; CO₂ + OH⁻ → HCO₃⁻ 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that storing CO₂ in the ocean this way doesn’t acidify the ocean, since we have added the necessary alkalinity to go with it.
There are many research proposals out there looking at the various ways to add alkalinity to the ocean to counteract it’s acidification and provide capacity to store the excess CO₂ in a pH neutral way. To learn more, this is a good place to start: &lt;a href=&quot;https://agupubs.onlinelibrary.wiley.com/doi/10.1002/2016RG000533&quot;&gt;Assessing ocean alkalinity for carbon sequestration&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Thu, 01 Jul 2021 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//climate/2021/07/01/calcium-carbonate.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//climate/2021/07/01/calcium-carbonate.html</guid>
        
        
        <category>climate</category>
        
      </item>
    
      <item>
        <title>Syringe pump &amp; pH stat </title>
        <description>&lt;p&gt;For an experiment I wanted to measure how quickly certain alkaline minerals, such as Mg(OH)₂ or MgSiO4 dissolve in seawater under constant pH.
For a large enough volume of water this is a reasonable assumption but I needed to do it in a small volume. That would require the use of a tiny
amount of Mg(OH)₂ and that would make it rather noisy to measure. However using a larger quantity doesn’t work because dissolving Mg(OH)₂ releases OH- ions and thus increases the pH, which significantly slows further dissolution. 
So I decided to use a pH-stat which would inject a tiny amount of HCl if the pH increased above the set point (8.1 in my case). 
The rate at which HCl was injected would then be directly proportional to the amount of Mg(OH)₂ that had dissolved. This means the injector 
needs to keep very accurate account of the amount of HCl dispensed.&lt;/p&gt;

&lt;p&gt;To build a simple pH stat I used a syringe (30ml) mounted on a linear motor connected to a controller driven by a Raspberry Pi.
The controller also has a pH electrode. This way the code on the Pi can run the control loop and actuate the linear motor whenever the pH 
increases while also keeping track of the exact movement of the motor and hence the exact quantity of HCl dispensed.&lt;/p&gt;

&lt;p&gt;The design was as follows:&lt;/p&gt;

&lt;h1 id=&quot;computer-controller&quot;&gt;Computer controller&lt;/h1&gt;

&lt;p&gt;A Raspberry Pi B+ as the central coordinating unit which will read from the pH probe and determine the syringe movements. Since I wanted log files and 
a full Unix system this was a preferred choice over using a Arduino or similar smaller microcontroller, though that’s a viable alternative. Critical are an I2C
port for communication with the pH probe, 2 GPIO outputs for stepping and direction control and two input GPIOs for the limit switches. 
Optional are additional GPIOs to dynamically set the step size (substepping).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://media.bechtle.com/is/180712/1c4b3d4ee288fc9434f5175bf56070570/c3/gallery/2a6926f9b6a84cfca84962fedad9fe40?version=0&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;ph-measurement&quot;&gt;pH Measurement&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;The pH electrode was purchased from Atlas Scientific (&lt;a href=&quot;https://atlas-scientific.com/probes/ph-probe/&quot;&gt;Gen 3 Lab Grade pH Probe&lt;/a&gt; )&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The pH probe amp/ADC was also from Atlas Scientific (&lt;a href=&quot;https://atlas-scientific.com/embedded-solutions/ezo-ph-circuit/&quot;&gt; EZO™ pH Circuit&lt;/a&gt; ) together with their 
carrier board (&lt;a href=&quot;https://atlas-scientific.com/carrier-boards/electrically-isolated-ezo-carrier-board-gen-2/&quot;&gt;Electrically Isolated EZO™ Carrier Board&lt;/a&gt; ) which provides
electrical isolation. These boards are addressable via I2C and can be essentially connected directly to the relevant pins on the Raspberry Pi. 
The board provides all the pH measurement and calibration commands necessary, via I2C.&lt;/p&gt;

    &lt;p&gt;&lt;img width=&quot;50%&quot; height=&quot;50%&quot; src=&quot;/assets/syringepump/EZO-Carrier-Board.webp&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;the-syringe-assembly&quot;&gt;The syringe assembly&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A linear rail stepper motor.  This is the one I used: &lt;a href=&quot;https://www.amazon.com/gp/product/B07K7FQ245&quot;&gt;100mm Linear Actuator NEMA 11 (NEMA11)&lt;/a&gt;
It works just like any stepper motor. This particular one wants 24V but your can actually drive it with 12V also, it just has less power.
The length was determined by the length of the travel of the syringe, plus some extra.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/syringepump/linear.jpg&quot; /&gt;
&lt;img src=&quot;/assets/syringepump/linear2.jpg&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A syringe. In version one I used a regular 30ml syringe with a Luer lock. From there I attached a short &lt;a href=&quot;https://www.amazon.com/Extension-Set-Luer-Lock-5-Pack&quot;&gt;Luer to Luer extension&lt;/a&gt; 
tube (about 10cm) and then to a &lt;a href=&quot;https://www.amazon.com/gp/product/B01HJ2B8H0&quot;&gt;Luer lock dispenser needle&lt;/a&gt;.
However I found that the plastic syringe and rubber plunger have too much internal flexibility and 
backlash and non linear response was a serious problem.  So I ended up redesigning it with a &lt;a href=&quot;https://www.amazon.com/gp/product/B00BQLLMYU&quot;&gt;30ml glass syringe&lt;/a&gt;.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/syringepump/syringe.jpg&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Some 3D printed parts to hold the syringe, the plunger and the limit switches in place. These have to be designed specific to the linear motor and the exact syringe used, but it is relatively straight forward to do in a CAD editor like &lt;a href=&quot;https://onshape.com&quot;&gt;OnShape&lt;/a&gt;.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/syringepump/syringe_holder.png&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;Here’s a closeup of the syringe mounted in the holder.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/syringepump/syringe_closeup.jpg&quot; /&gt;&lt;/p&gt;

    &lt;p&gt;The STEP files for the assembly are here: &lt;a href=&quot;/assets/syringepump/syringe_holder.step&quot;&gt;Syringe Holder.step&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Two limit switches at either side of the linear stepper motod caddy, which signal back to the Pi that the end of the travel was reached. These receive 
3V power from the Pi and then connect in series back to the Pi to a GPIO input pin. For visual confirmation I also added an LED via a resistor to ground, 
the Pi GPIO pins can sink supply current to drive these directly without a transistor.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/syringepump/limit1.jpg&quot; /&gt;
&lt;img src=&quot;/assets/syringepump/limit2.jpg&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;To drive the motor I used a &lt;a href=&quot;https://www.pololu.com/product/2133&quot;&gt;DRV8825 High Current Stepper Motor Driver Carrier&lt;/a&gt;. This board takes in 12V or 24V power and 3V logic signals
for the direction and the steps. The four outputs are connected to the two coils inside the stepper motor. The STEP and DIRECTION pins are connected to GPIOs
on the Pi and are driven directly by the code, since the precise speed/timing doesn’t matter so much as the precise control over the number of steps.&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/assets/syringepump/drv8825.png&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;a-pcb-board-to-keep-it-all-together&quot;&gt;A PCB board to keep it all together.&lt;/h1&gt;

&lt;p&gt;This isn’t strictly necessary as most of the parts could just be wired together directly but I decided to 
to build a Raspberry Pi extension board with a GPIO header that would sit directly on top of the Pi and coordinate all the other elements.
This has the advantage that everything is a lot neater and not as brittle, with patch wires running everywhere. 
The ciruit is quite simple:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/syringepump/pump_schematic.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The board hosts the DRV8825. Pins VMOT is connected to the 12V or 24V motor power supply (and 100uF cap to ground). Pins B1, B2 and A1, A2 connect to the coild on th emotor. Which pair is connected to which coil will determine the direction but that’s easily switchable in software so it doesn’t matter at assembly time. 
Pin DIR sets the direction, set to high mean one way, set to low means the other way. This is directly connected to the header on GPIO21.
The pin STEP drives the motor. Every rising edge on this pin steps the motor one step or microstep. 
The pins MS1-3 set the microstep size. In this case I chose the finest microstepping available, 1/32 steps, by setting all three pins to high. 
Howevver to keep things configurable later i decided to provide the setting both by jumpers on the board but also by GPIO output pins in case I later decided
I needed to change things dynamically.&lt;/p&gt;

&lt;p&gt;The board also has a barrel plug for the 12V/24V supply and an 8 pin connector to go to the linear motor.&lt;/p&gt;

&lt;p&gt;I designed a board on &lt;a href=&quot;https://easyeda.com/&quot;&gt;EasyEDA&lt;/a&gt; and had it manufactured. The Gerber files for the PCB are downloadable here:&lt;/p&gt;

&lt;p&gt;&amp;lt;a href=”“/assets/syringepump/syringepump_gerber.zip”&amp;gt;[Gerber ZIP file]&amp;lt;/a&amp;gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/syringepump/pump_pcb_size1.png&quot; /&gt;
&lt;img src=&quot;/assets/syringepump/pump_pcb_size2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After soldering everything together the whole assembly looked like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/syringepump/board1.jpg&quot; /&gt;
&lt;img src=&quot;/assets/syringepump/board2.jpg&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;final-assembly&quot;&gt;Final assembly&lt;/h1&gt;

&lt;p&gt;Putting it all together on an acrylic lasercut board:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/syringepump/overall.jpg&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;software&quot;&gt;Software&lt;/h1&gt;

&lt;p&gt;Below is some example code on how to drive the syringe. The VOLUME_PER_STEP
constant has to be determined empirically. To do this accurately I’d let the
motor draw a full syringe of distilled water, and then dispense virtually all the liquid into a 
preweighed beaker. I can then weigh on a precision scale again and divide the number of steps taken.
Distilled water is pretty damn close to a density of 1 so that was good enough for me.
For an even more accurate volume determination one could calibrate the density of the water with a 
precision bulb pipette. The AtlasI2C library can be obtained from their website.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/python3
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;RPi.GPIO&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;AtlasI2C&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AtlasI2C&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;threading&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Thread&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;DIR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# Direction GPIO Pin
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STEP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Step GPIO Pin
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FORWARD_STOP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# Limit switch forward GPIO Pin
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BACKWARD_STOP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Limit switch backward GPIO Pin
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STOP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BACKWARD_STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FORWARD_STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;FORWARD&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;# Clockwise Rotation
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BACKWARD&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# Counterclockwise Rotation
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SPR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# Steps per Revolution (360 / 7.5)
&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;## Glass 30ml syringe
# This must be measured empirically
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VOLUME_PER_STEP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;6.277352941E-8&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Liters per microstep
&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setmode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BCM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DIR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STEP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FORWARD_STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pull_up_down&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PUD_DOWN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BACKWARD_STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pull_up_down&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PUD_DOWN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DIR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FORWARD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;_pump_pwm_thread_active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;_pump_pwm_thread&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;_pump_pwm_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;_pump_pwm_mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_ph_thread_call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_pump_pwm_thread_active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_pump_pwm_count&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread_active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;now_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;direct&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DIR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;direct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.001&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;direct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]):&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0000855&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;#print(&quot;[EDGE] block=&quot;,block, &quot; t=&quot;,time.time())
&lt;/span&gt;      &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STEP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_mask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HIGH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STEP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_mask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_pump_pwm_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;start_pump_pwm_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread_active&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread_active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_ph_thread_call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stop_pump_pwm_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread_active&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread_active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread_active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pump_pwm_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_pump_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pump_pwm_speed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#print(&quot;Set rate to : &quot;, _pump_pwm_speed)
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_pump_position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_count&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_pump_dry_run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dry_run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_mask&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dry_run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_mask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;blocking_move_steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pump_pwm_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pump_pwm_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;now_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DIR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;direct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0000855&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STOP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;direct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]):&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hit backstop.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;speed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STEP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HIGH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STEP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pump_pwm_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pump_pwm_count&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#############################################
# Interface as command line tool 
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argparse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ArgumentParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;cmd&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;command&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;--speed&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;steps/s (1-4000)&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;--steps&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;--volume&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;volume in ml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;--threaded&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;store_true&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Threaded mode is inaccurate in total number of steps, if speed is high&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bwd&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fwd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[cmd] must be &apos;bwd&apos; or &apos;fwd&apos;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;direct&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fwd&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;volume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;volume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1E-3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VOLUME_PER_STEP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Going %d steps at speed %f steps/s... (Volume = %f.3ml)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1E3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VOLUME_PER_STEP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;threaded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Asynchronous version
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;start_pump_pwm_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;set_pump_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stop_pump_pwm_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Final steps: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pump_pwm_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;             &lt;span class=&quot;c1&quot;&gt;# Blocking version
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;pump_pwm_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;blocking_move_steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;speed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Final steps: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pump_pwm_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Done.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;__main__&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;ph-measurement-code&quot;&gt;pH measurement code&lt;/h2&gt;

&lt;p&gt;Below is example code which will take pH measurements from the pH
electrode. On disadvantage of the Atlas measurement board is that it’s fairly
slow. A measurement takes ~1s, which makes fast reaction tricky.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/python3
&lt;/span&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;time&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;AtlasI2C&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	 &lt;span class=&quot;n&quot;&gt;AtlasI2C&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;argparse&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;threading&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Thread&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print_devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;--&amp;gt; &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_device_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; - &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_device_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AtlasI2C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device_address_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_i2c_devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_address_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device_address_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set_i2c_address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;I&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;startswith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;moduletype&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name,?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AtlasI2C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;moduletype&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;moduletype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;# Background pH service (Seperate thread)
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_pH_thread_active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;_pH_thread&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;_pH_readings&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;_pH_reading_times&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_ph_thread_call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pH_finetune&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_readings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_reading_times&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_thread_active&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_thread_active&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;now_time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;pHstring&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;R&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;IOError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;PH query failed. IO Error&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;pH&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pHstring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pH_finetune&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;PH value invalid. ValueError&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;_pH_readings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;start_ph_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_thread_active&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;print_devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pH_thread_active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pH_thread&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_ph_thread_call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pH_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stop_ph_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;global&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_thread_active&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;_pH_thread_active&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_pH_thread&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_pH_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#############################################
# Interface as command line tool 
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;usage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
   ph.py c                Measure pH continuously 
   ph.py cal,mid,7.01     Calibrate Midpoint
   ph.py cal,low,4.00     Calibrate Lowpoint
   ph.py cal,high,10.05   Calibrate Highpoint
  &quot;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argparse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ArgumentParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;cmd&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;help&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parse_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;print_devices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Continuous reading
&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print_ph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start_ph_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print_ph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;device&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;IOError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Query failed &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; - Address may be invalid, use list command to see available addresses&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;__main__&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To calibrate the electrode there are several commands that can be issued.
Putting the electrode into the respective pH buffer, the following commands
can be used to calibrate the pH board.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#python3 ph.py cal,mid,7.01
#python3 ph.py cal,high,10.04
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can now continuously read pH values.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python3 ph.py c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;From here the possibilities are endless. One could program time controlled or pH controlled releases. Acid-base auto-titration is pretty easy, though it’s
important to think about overshoot and the non-linearity of the pH scale.&lt;/p&gt;
</description>
        <pubDate>Sat, 13 Mar 2021 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//climate/2021/03/13/syringe-pump.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//climate/2021/03/13/syringe-pump.html</guid>
        
        
        <category>climate</category>
        
      </item>
    
      <item>
        <title>Shrink Raspberry Pi Images</title>
        <description>&lt;p&gt;I often make images of Raspberry Pi sdcards for easy installation and cloning. 
Unfortunately the size of the image will always be that of the size of the card, 
which is usually much larger than the amount of actual data.
Thankfully there is a way to shrink an image, though every tutorial i’ve seen
online is cumbersome and manual (e.g. using gparted). Instead I 
&lt;a href=&quot;https://github.com/mtyka/shrinkwrap&quot;&gt;wrote a script&lt;/a&gt;
that does it automatically and shrinks the image to it’s minimal size.&lt;/p&gt;

&lt;p&gt;NOTE: USE AT OWN RISK - THIS IS UNTESTED. ALWAYS BACKUP YOUR IMAGE BEFORE TRYING
THIS.&lt;/p&gt;

&lt;h2 id=&quot;clone-the-script&quot;&gt;Clone the script&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/mtyka/shrinkwrap.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;shrinkwrap
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;copy-the-sd-card-over&quot;&gt;Copy the sd card over&lt;/h2&gt;

&lt;p&gt;Find your sdcard device, might be /dev/sdb or /dev/mmcblk0 or other.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lsblk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Copy the image locally to an img file&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo dd &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4M &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/mmcblk0 &lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;myimage.img &lt;span class=&quot;nv&quot;&gt;conv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;fsync &lt;span class=&quot;nv&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;shrink-the-image&quot;&gt;Shrink the image&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./shrinkwrap.sh myimage.img
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;copy-the-image-to-new-sd-card&quot;&gt;Copy the image to new sd card&lt;/h2&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo dd &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4M &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;myimage.img &lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/mmcblk0.img &lt;span class=&quot;nv&quot;&gt;conv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;fsync &lt;span class=&quot;nv&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;boot-from-the-card&quot;&gt;Boot from the card&lt;/h2&gt;

&lt;p&gt;You can now resize the image back to take the full SD card size by going to:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo raspi-config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then choose “Expand root partition to fill SD card” option under Advanced 
Options.&lt;/p&gt;

</description>
        <pubDate>Thu, 25 Jul 2019 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//raspberrypi/2019/07/25/shrink-raspberrypi-image.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//raspberrypi/2019/07/25/shrink-raspberrypi-image.html</guid>
        
        
        <category>raspberrypi</category>
        
      </item>
    
      <item>
        <title>Headless RPi - prevent SD corruption</title>
        <description>&lt;p&gt;Raspberry Pis are perfect when building interactive art projects (e.g. &lt;a href=&quot;http://www.miketyka.com/?p=usandthem&quot;&gt;20 printers printing ML generated tweets&lt;/a&gt;
and often it’s nice to leave the Pi headless, i.e. without keyboard or monitor. And it’s convenient to just power up the device to start it and pull the power when its time to shut it down. However there are two minor issues - how do you get your code to start without a login and how do you precent system corruption. Thanksfully there are solutions to that.&lt;/p&gt;

&lt;h2 id=&quot;startup-starting-your-code-on-startup&quot;&gt;Startup: Starting your code on startup.&lt;/h2&gt;

&lt;p&gt;There a &lt;a href=&quot;https://www.dexterindustries.com/howto/run-a-program-on-your-raspberry-pi-at-startup/&quot;&gt;many solutions&lt;/a&gt; for this 
but personally I like to run my code as a service. To do so add a file like the one below to /etc/rc.d/ and make it executable.
This assumes your startup script lives in /home/you/mycode/run.sh and exepects to be run from that directory.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#! /bin/sh

### BEGIN INIT INFO
# Provides:             tweet_printer
# Required-Start:       $remote_fs $syslog
# Required-Stop:        $remote_fs $syslog
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Tweet stream deamon
### END INIT INFO

. /lib/lsb/init-functions

start() {
  log_action_begin_msg &quot;Starting tweet printer daemon&quot;
	cd /home/you/mycode/
	sh run.sh # OR python3 run.oy OR ./myexe 
  log_action_end_msg
}

stop() {
  log_action_begin_msg &quot;Stopping tweet printer daemon&quot;
  # &amp;lt;Insert command to kill your process here&amp;gt;
  #
  log_action_end_msg
}

case &quot;$1&quot; in
    start)
      start
  ;;
    stop)
      stop
  ;;
    restart)
      stop
      start
  ;;
    *)
      echo &quot;Usage: &amp;lt;MYSERVICENAME&amp;gt; {start|stop|restart}&quot;
      exit 1
  ;;
esac
exit 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After making the file, run&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo chmod +x /etc/init.d/myscript 
sudo update-rc.d /etc/init.d/myscript defaults
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note your code will run as root.  Note this can also be a python program directly but i prefer the shell script to keep things more seperated.&lt;/p&gt;

&lt;h2 id=&quot;shutdown-preventing-sd-corruption&quot;&gt;Shutdown: Preventing SD corruption&lt;/h2&gt;

&lt;p&gt;Linux systems do not like to to be powered off suddenly and this can lead to corruption of the filesystem on the SD card. 
Last time we faced this issue we researched a bunch of UPS-like soutions that would detect power failure and provide both bridge-over 
power and a signal to the Pi to cleanly shut down using extra electronics and super caps or batteries. 
However, there’s a much much simpler solution: just mount
the filesystem read only. This is not 100% straightforward and can have some drawbacks but for what we were doing it was perfect.
Doing so makes the entire system completely stateless and thus unable to corrupt itself. THe drawback is you cannot persist any state from one boot to the next but there are aalso workarounds which we’ll discuss at the end. 
I’m basically following the instructions from &lt;a href=&quot;http://ideaheap.com/2013/07/stopping-sd-card-corruption-on-a-raspberry-pi/&quot;&gt;http://ideaheap.com/2013/07/stopping-sd-card-corruption-on-a-raspberry-pi/&lt;/a&gt; here, except that
I go one more step and fully mount the system read-only. The lock/unlock scripts work around the usability issue.&lt;/p&gt;

&lt;p&gt;0) Disable swapping&lt;/p&gt;

&lt;p&gt;This will disable swapping:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dphys-swapfile swapoff
sudo dphys-swapfile uninstall
sudo update-rc.d dphys-swapfile remove

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;free -m&lt;/code&gt; shows the swap to be 0&lt;/p&gt;

&lt;p&gt;1) Set up /etc/fstab to mount /var/log and /var/tmp using tmpfs.
The system needs scratch space to write into, so just mounting the root filesystem read-only doesnt work very well.
However we can setup in-RAM filesystem for this purpose.
Add these two lines to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/fstab&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;none                  /var/log        tmpfs   size=1M,noatime   0       0
none                  /var/tmp        tmpfs   size=1M,noatime   0       0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then also add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ro&lt;/code&gt; to the line that mounts your root and boot file system. In the end the file should now look someting like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;proc                  /proc           proc    defaults          0       0
PARTUUID=d6a29f93-01  /boot           vfat    ro,noatime        0       2
PARTUUID=d6a29f93-02  /               ext4    ro,noatime        0       1
none                  /var/log        tmpfs   size=1M,noatime   0       0
none                  /var/tmp        tmpfs   size=1M,noatime   0       0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now the system will be stateless. However sometime its necesseray to unlock it to make mods:&lt;/p&gt;

&lt;p&gt;2) Set up two script that will allow us to temporarily lock and unlock the filesystem if we need to edit something.&lt;/p&gt;

&lt;p&gt;~/lock.sh&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/sh
mount -o remount,ro $(mount | grep &quot; on / &quot; | awk &apos;{print $1}&apos;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;~/unlock.sh&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/sh
mount -o remount,rw $(mount | grep &quot; on / &quot; | awk &apos;{print $1}&apos;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re updating kernels etc oyu may also have to remount /boot but that’s not as frequently necessary so I dont set up a script for that.&lt;/p&gt;

&lt;p&gt;4) Hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo reboot&lt;/code&gt;  check your service starts up&lt;/p&gt;

&lt;h2 id=&quot;drawbacks-of-this-method&quot;&gt;Drawbacks of this method&lt;/h2&gt;
&lt;p&gt;In my experience this works really well in practice, but there could be drawbacks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You have no persistent system log - if something went wrong it can make debugging hard.&lt;/li&gt;
  &lt;li&gt;You can’t persist state from one boot to the next. One work around is to have a second SD card using a 
USB stick which is mounted periodically just when needed and then unmounted. Or you can briefly remount &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/&lt;/code&gt; read-write, write your state and then remount again.
Not perfect (if the power goes of right that second) but it makes it way less likely. Or, if you hae network, you could send the state to a farawy server.&lt;/li&gt;
  &lt;li&gt;The tmp dirs that are in memory fill up you have an issue.&lt;/li&gt;
  &lt;li&gt;Not having a swap partition, if you run out of memory, you’ll likely crash.&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Tue, 09 Oct 2018 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//hardware/2018/10/09/read-only-pi.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//hardware/2018/10/09/read-only-pi.html</guid>
        
        
        <category>hardware</category>
        
      </item>
    
      <item>
        <title>Glass etching</title>
        <description>&lt;p&gt;When casting glass I usually need to sandblast the glass after cold working in order to get a smooth and consistent 
texture across the piece. I usually use a 150grit medium at 35 psi or so.&lt;/p&gt;

&lt;p&gt;Sandblasting will make everything really even and nice, but results in a very whitish, extremely scattery surface
that lets little light shine through. The reason is that sandblasting essentially knocks tiny chunks out of the surface of the glass
(every time a sand particle collides with the glass) and results in a surface that has lots of sharp edges, each refracting and scattering light. Any liquid or moisture on the surface will smooth the surface and make it more transluscent and thus oils from hands etc show up in really ugly ways.&lt;/p&gt;

&lt;p&gt;To get a more consistently transluscent surface and no more finger prints you have two options to smooth the microscopic sharp edges. Either cover the glass in a 
varnish, like &lt;a href=&quot;https://www.hisglassworks.com/support/etching-and-coating/coating-sandblasted-surfaces-with-liquid-luster.html&quot;&gt;Liquid Luster&lt;/a&gt;. 
I don’t like this option because it just seems wrong to cover the glass in plastic. It also feels weird to touch and doesn’t look quite as nice.
The other option is to lightly etch the glass - effectively rounding out the sharp edges, chemically. The disadvantage of this option is that it involves some 
quite dangerous chemicals. Silicates (SiO4) do not dissolve in much, but they are attached by hydroflouric acid and related compounds.
HF is incredibly toxic and dangerous stuff - pretty much not feasible outside of a lab or an industrial setting.&lt;/p&gt;

&lt;p&gt;Somewhat more managable but still very dangerous is Ammonium Biflouride. His Glassworks sells a product called &lt;a href=&quot;https://www.hisglassworks.com/bond-and-etch/acid-etching/vari-etch-powder.html&quot;&gt;Varietch&lt;/a&gt;  which consists of ~50% Ammoniumbiflouride and ~50% sucrose (C12H22O11), sold as a powder. The sucrose is inert here and acts presumably as filler and also a hygroscopic, keeping the power wet-ish (you don’t want to inhale Ammoniumflouride powder). Or perhaps they add it to increase the viscosity of the solution ? I’m not sure. Either way you dissolve it in water and it etches glass very nicely at slightly reduced risk (You still need to use thick rubber gloves, a respirator and eye protection etc. &lt;a href=&quot;https://en.wikipedia.org/wiki/Fluoride_toxicity&quot;&gt;Flouride poisoning&lt;/a&gt; is extremely dangerous).&lt;/p&gt;

&lt;p&gt;One problem I’ve encountered is with lead glass (crystal). Lead glass is beautiful and easier to cast (it less viscous at a given temperature). However when etching lead glass with ammonium biflouride
the surface gets covered in a thick white substance which is insoluble and hard to scrub off. For years I’d just use muscle grease and scrub it off, or sometimes a pressure washer. I noticed that Soda glass does not form this residue. 
Recently I thought about what this stuff must be and came up with a better (albeit also hazardous) solution.
Lead glass is a mixture of SiO4, PbO and K2O. Reacting with NH4HF2, these yield SiF4, PbF2 and KF. Now, the first and last are water soluble, but PbF2 is in fact a white, water-insoluble
solid and thus most likely the white residue I find when etching my lead glass.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/leadflouride.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After some research I found that lead flouride (PbF2) is soluble in nitric and in hydrochloric acid.
So I did two experiments to check my suspicion. First i etched a piece of lead glass and a piece of soda-lime glass. As expected, only the lead lead glass developed the residue.
Second I got some muriatic (hydrochloric) acid from the hardware store (used to etch concrete). This is ~32% HCl. I soaked the residue covered piece of leadcrystal for ~ 1 hr and to my delight
the residue was completely dissoved.&lt;/p&gt;

&lt;p&gt;Obviously using concentrated hydrochloric acid is dangerous and needs to be done with great care and with good ventilation and safety equipment (Rubber gloves, Eye protection, Face shield, rubber apron). Note that all that is already necessary when dealing with the Varietch
Ammoniumbiflouride, so it doesnt make the overall process much worse.&lt;/p&gt;

&lt;p&gt;See also &lt;a href=&quot;https://people.maths.ox.ac.uk/fowler/papers/2011.5.pdf&quot;&gt;This paper&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;more-notes&quot;&gt;More notes:&lt;/h3&gt;

&lt;p&gt;32% HCl, dissolves within 1 hr
10% HCl, did not dissolve in 12 hrs.&lt;/p&gt;

</description>
        <pubDate>Tue, 09 Oct 2018 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//glass/2018/10/09/glass-etching.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//glass/2018/10/09/glass-etching.html</guid>
        
        
        <category>glass</category>
        
      </item>
    
      <item>
        <title>Superresolution with semantic guide</title>
        <description>&lt;p&gt;A few months ago I posted some results from experiments with &lt;a href=&quot;/machine/learning/2017/06/06/highres-gan-faces.html&quot;&gt;highresolution GAN-generated faces&lt;/a&gt;. By popular request here is a little more on the approach taken and some newer results.&lt;/p&gt;

&lt;p&gt;Generative machine learning has made tremendous strides in recent years. Unfortunately most models are limited to resolutions below 256x256 due to memory and processing limitations. For larger resolutions two-step methods have been proposed such as &lt;a href=&quot;https://arxiv.org/abs/1612.03242&quot;&gt;StackGAN&lt;/a&gt; where an initial GAN generates a low-resolution sample from a seed random distribution and then a second, conditional GAN generates a high resolution image from the initial sample. To get high resolution faces, I built on that idea but in order to get up to 768x768 or larger I apply the second up-res stage in tiles, taking in small crops of the initial and increasing their resolution 8x. Overlapping tiles are then merged and feathered into the final image.&lt;/p&gt;

&lt;h1 id=&quot;details&quot;&gt;Details&lt;/h1&gt;

&lt;p&gt;A typical up-resolution network (e.g &lt;a href=&quot;https://arxiv.org/abs/1609.04802&quot;&gt;Ledig et al.&lt;/a&gt; ) is a generative network which is conditioned on a low-resolution input patch and produces a plausible matching highres image. This means that the early parts of the network have to essentially interpret the semantic content of the lowres image in order to decide which highres features and textures to generate. This in itself is effectively an image segmentation/recognition task and is made especially difficult because much of the information needed is contextual, i.e. located outside the input patch and thus unavailable to the network during both training and inference.
To help with this issue, for the narrow case of high-resolution face generation, one can supplement semantic information in the form of additional input channels. This information can be generated from the full low-res image (not just the crop) and thus provide the additional semantic guidance. Similar ideas have been used in prior work for examples (&lt;a href=&quot;https://arxiv.org/abs/1611.07004&quot;&gt;Isola et al&lt;/a&gt;, &lt;a href=&quot;https://arxiv.org/pdf/1603.01768.pdf&quot;&gt;Champagnard et al.&lt;/a&gt; and recently in &lt;a href=&quot;https://arxiv.org/abs/1707.09405&quot;&gt;Chen et al.&lt;/a&gt; ).&lt;/p&gt;

&lt;p&gt;First a lowres image is generated using a typical DCGAN at 128x128. Since this is a pretty standard procedure the following will focus on the high resolution step(s).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/method1.png&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;semantic-guide&quot;&gt;Semantic guide&lt;/h1&gt;

&lt;p&gt;The lowres face is then processed through a facial landmark algorithm (using &lt;a href=&quot;http://dlib.net/&quot;&gt;DLIB&lt;/a&gt;) to generate a facial feature guide. For simplicity the guide is encoded simply as colors in RGB. Alternatively each feature could be saved in its own channel. The mask is depth-stacked with the low-res image (to make a 128x128x6 image) and then downsampled slightly to 96x96x6. The downsampling steps helps to smooth out artifacts in the lowres image that came from the lowres GAN and also conveniently anti-aliases the guide.
For the next step,  individual 32x32x6 crops of this input are fed into the second stage, the up-resolution network.&lt;/p&gt;

&lt;h1 id=&quot;upres-generator&quot;&gt;Upres generator&lt;/h1&gt;

&lt;p&gt;The upres stage is a &lt;a href=&quot;https://arxiv.org/pdf/1706.03142.pdf&quot;&gt;J-shaped network&lt;/a&gt;, similar to a &lt;a href=&quot;https://arxiv.org/abs/1505.04597&quot;&gt;U-Net&lt;/a&gt;: The encoder side of the network is truncated (starting at 32x32x6) and has 4x fewer channels compared to the decoder side. The reduction increases training speed and reduces parameters. Due to the presence of the semantic guide the encoder side of the network can afford to be slimmer.
The decoder side has 2 additional convolutional layers between each deconv step, all the way up to 256x256, thus achieving 8x upsampling. The additional layers beef up the modeling capacity of the network and seem to help generate more elaborate textures. 
Skip connections are added between layers of equal x,y extent on either side of the U to improve gradient flow and give access to the lowres features at most places in the network, just like in the original U-net. Compared to pix2pix I’ve also removed the batchnorm step which doesn’t appear to help, slows down training and also means that inference and training use different normalization parameters.&lt;/p&gt;

&lt;h1 id=&quot;discriminator&quot;&gt;Discriminator&lt;/h1&gt;

&lt;p&gt;The discriminator takes the highres output, as well as the lowres 32x32x6 input and determines whether the highres is real or not and whether it is a plausible highres version of the lowres input. It’s structure is identical to that used in pix2pix (&lt;a href=&quot;https://arxiv.org/abs/1611.07004&quot;&gt;Isola et al&lt;/a&gt; ) except that the 32x32x6 input is simply concatenated with the feature channels after 4 down samplings of the highres input when the discriminator spatial extent has reached 32x32.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/method2.png&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;lowres-l1-loss&quot;&gt;Lowres L1 loss&lt;/h1&gt;

&lt;p&gt;Similar to pix2pix, the generator loss is composed of a GAN loss and a direct L1 pixel loss. 
However the L1 loss is applied to a &lt;i&gt;downsampled pair&lt;/i&gt;  of images (32x32, using avgpooling) rather than the full 256x256. In other words the L1 loss only ensures that the down-sampled output of the generator is a plausible source for the 32x32 input rather than an exact reproduction of the training data. We want to avoid penalizing the generator for getting the exact details wrong - instead fine details are evaluated only by the GAN loss which only assesses realism in general, not an exact match to each training example. This idea is similar in spirit to the Laplacian loss used by &lt;a href=&quot;https://arxiv.org/abs/1707.05776&quot;&gt;Bojanowski et al.&lt;/a&gt; except that the highres evaluation is left to a GAN rather than matching the Laplacian pyramids.&lt;/p&gt;

&lt;h1 id=&quot;inference--tiling&quot;&gt;Inference &amp;amp; Tiling.&lt;/h1&gt;

&lt;p&gt;To reduce tiling artifacts, overlapping tiles are generated and blended together. I also present each tile twice, once flipped in x. Other aspects of the input can also be varied (contrast, saturation). This is because the quality of the upres differs stochastically depending on the input. After transforming back, one can compare the quality against some metric. One possible metric is running each output hrough the trained discriminator and using it’s output to pick the most “realistic”. Alternatively simply measuring contrast or the variance of the laplacian of the output (to detect sharpness) can be used.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/full_tiles.jpg&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;interesting-insights-ymmv&quot;&gt;Interesting insights (YMMV):&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;Semantic guiding is a powerful tool to assist generative networks. Very recent work by Chen et al., has impressively demonstrated this also.&lt;/li&gt;
  &lt;li&gt;Batchnorm doesn’t always help. I found batchnorm weird and annoying on the generator path. Sometimes using batch averages at inference time worked 
better than using the averaged values. Also tried per sample batchnorm, i.e. normalizing just over the x,y dimensions but not over the batch which sometimes worked better. In the end I found that just disabling batchnorm all-together does not degrade performance but gets around some of the inference-time instabilities. I still use batchnorm in the discriminator but in the generator it’s nice to not have it.&lt;/li&gt;
  &lt;li&gt;Applying L1 at a downsampled scale gives more room for the generator to in-paint details free of constraints, other than realism.&lt;/li&gt;
  &lt;li&gt;Garbage in/garbage out: Putting in the time to clean your data set of bad data points (blurry pictures, drawings, half-tone, artifacts, occluded faces etc) really pays off, even if it reduces the size of your dataset. It seems that a smaller high quality set is much more effective than a large dataset with poor quality images. I wrote a handy little tool &lt;a href=&quot;https://github.com/mtyka/swipelabel&quot;&gt;SwipeLabel&lt;/a&gt; for my phone that can be used to sort images with swipe gestures. It’s easy to use and you can sort your data while on the bus, while bouncing a baby, while waiting in line etc..&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;latest-results&quot;&gt;Latest results:&lt;/h2&gt;

&lt;p&gt;Some more recent results below. Getting better skin texture but hair seems to have gotten worse.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/round2/round2_1.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/round2/round2_2.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/round2/round2_3.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/round2/round2_4.jpg&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;video&quot;&gt;Video&lt;/h2&gt;

&lt;p&gt;I’m experimenting too with doing some smooth interpolations in z space and then upressing each frame. Unfortuantely the upres step isn’t super stable from frame to frame. Frame interpolation helps a little bit with that problem. Anyway, it makes an interesting looking video:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/VM5svwt8Jk8&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

</description>
        <pubDate>Wed, 09 Aug 2017 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//machine/learning/2017/08/09/highres-gan-faces-followup.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//machine/learning/2017/08/09/highres-gan-faces-followup.html</guid>
        
        
        <category>machine</category>
        
        <category>learning</category>
        
      </item>
    
      <item>
        <title>Work in progress: Portraits of Imaginary People</title>
        <description>&lt;p&gt;For a while now I’ve been experimenting with ways to use generative neural nets to make portraits. Early experiments were based on deepdream-like &lt;a href=&quot;https://twitter.com/mtyka/status/767116516252712960&quot;&gt;approaches&lt;/a&gt; using backprop to the image but lately I’ve focused on GANs. As always resolution and fine detail is really difficult to achieve. For starters the receptive field of thse networks is usually less than 256x256 pixels. One way around this is tiling combined with stacking GANs, which many people have experimented with, for example this paper uses a two-stage GAN to get high resolution: (https://arxiv.org/abs/1612.03242).&lt;/p&gt;

&lt;p&gt;I tried a similar approach and I’ve been finally been having some more success upres-ing GAN-generated faces to 768x768 pixels in two stages and in some cases as far as &lt;a href=&quot;/assets/highresgan/4k/00098000.jpg&quot;&gt;4k x 4k&lt;/a&gt;, using three stages. This gives them a lot more crisp detail. Since I’m trying to do this with art in mind, I don’t mind if the results are not necessarily realistic but fine texture is important no matter what even if it’s surreal but highres texture.&lt;/p&gt;

&lt;p&gt;As usual I’m battling mode collapse and poor controllability of the results and a bunch of trickery is necessary to reduce the amount of artifacts. Specifically the second stage GAN is meta stable between smooth skin and hairy skin and often results in patchy output. For now I’m using vanilla GANs and these results are fairly cherry-picked - I should give WGAN, CramerGAN or BEGAN a shot, word is they converge better. BEGAN seems to have a smoother, VAE kinda look which perhaps makes sense because the discriminator is an auto encoder and the loss is an indirect reconstruction loss.&lt;/p&gt;

&lt;p&gt;Anyways, here are some highly cherry-picked work-inprogress results:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00098000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00128000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00212000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00664000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00236000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00266000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00304000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00344000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00346000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00360000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00376000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00444000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00349000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00734000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The quality depends strongly on the realism of the lowres output. I typically generate things at 128x128 or 256x256 at the first stage and then upres to 768x768 or 1024x1024 at the second stage. In most cases the quality is nowhere near the above, but in some cases the results are kinda interesting artistically. Occasionally a sort of artistic style emerges.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/moreartsy/00706000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/moreartsy/00569000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/00436000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/moreartsy/s.000001.000.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/moreartsy/s.000001.047.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Adding a third stage allows upressing up to 4k. However I dont have any actual training data at that resolution, meaning the network only learns to generally predict smooth edges etc, It can’t know the details of what skin pores or eyelashes look like. A super-highres database of faces would be needed here. Still for purposes of printing it’s nicer to create some interesting looking artifacts at this resolution, rather than bilinear interpolation of just pixelation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/4k/00098000.4k.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/highresgan/4k/00236000.4k.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Some full size 4k ones (click to enlarge):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/highresgan/4k/00236000.full4k.jpg&quot;&gt;&lt;img width=&quot;350&quot; height=&quot;auto&quot; src=&quot;/assets/highresgan/4k/00236000.full4k.jpg&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;/assets/highresgan/4k/00738000.full4k.jpg&quot;&gt;&lt;img width=&quot;350&quot; height=&quot;auto&quot; src=&quot;/assets/highresgan/4k/00738000.full4k.jpg&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyways, the goal is to make these into printable physical-world art pieces but I found in practice the resolution and detail has to be pretty high or it just doesn’t look nice printed. Like I said, it’s all work in flux and progress, more soon.&lt;/p&gt;

&lt;h2 id=&quot;followup-method&quot;&gt;Followup: Method&lt;/h2&gt;

&lt;p class=&quot;post-meta&quot;&gt;Aug 8th, 2017&lt;/p&gt;

&lt;p&gt;By popular request a &lt;a href=&quot;/machine/learning/2017/08/09/highres-gan-faces-followup.html&quot;&gt;little more on the approach taken here&lt;/a&gt;.&lt;/p&gt;

</description>
        <pubDate>Tue, 06 Jun 2017 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//machine/learning/2017/06/06/highres-gan-faces.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//machine/learning/2017/06/06/highres-gan-faces.html</guid>
        
        
        <category>machine</category>
        
        <category>learning</category>
        
      </item>
    
      <item>
        <title>Square to Hex</title>
        <description>&lt;p&gt;Imagine you had a square grid of points and you’d like to transform that grid into a hexagonal grid of points such that the local relationships between points are preserved. For irregular or arbitrary start or target grids tools like Mario Klingemann’s &lt;a href=&quot;https://github.com/Quasimondo/RasterFairy&quot;&gt;RasterFairy&lt;/a&gt; do an excellent job. However for a regular to regular transform I was wondering if there were optimal, regular, tilable solutions.&lt;/p&gt;

&lt;p&gt;The naive way to transform from square to hex is to simply take every second row and shift it by half the gridspacing. That gives you triangles and you’re done. However, unfortunately, the triangles are now not equilateral, being stretched in y by sqrt(3)/2. Ok, you say, why not just just rescale in y by 2/sqrt(3), and now you’re done. Ok, but now you’ve changed the &lt;em&gt;aspect ratio&lt;/em&gt; of the points. What if you wanted to preserve the aspect ratio &lt;em&gt;and&lt;/em&gt; get an equilateral, hexagonal arrangement &lt;em&gt;and&lt;/em&gt; minimize the grid distortion.&lt;/p&gt;

&lt;p&gt;Obviosuly there can’t be perfect solutions to this problem since sqrt(3) is irrational.
However we can find grid arrangements whose aspect ratio change is very close to 1.0&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Square&lt;/th&gt;
      &lt;th&gt;Hexagonal&lt;/th&gt;
      &lt;th&gt;Aspect Devation&lt;/th&gt;
      &lt;th&gt;Points&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;4x4&lt;/td&gt;
      &lt;td&gt;4x4&lt;/td&gt;
      &lt;td&gt;0.866&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4x6&lt;/td&gt;
      &lt;td&gt;4x6&lt;/td&gt;
      &lt;td&gt;0.866&lt;/td&gt;
      &lt;td&gt;24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;16x14&lt;/td&gt;
      &lt;td&gt;14x16&lt;/td&gt;
      &lt;td&gt;1.131&lt;/td&gt;
      &lt;td&gt;224&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;18x16&lt;/td&gt;
      &lt;td&gt;16x18&lt;/td&gt;
      &lt;td&gt;1.096&lt;/td&gt;
      &lt;td&gt;288&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;20x18&lt;/td&gt;
      &lt;td&gt;18x20&lt;/td&gt;
      &lt;td&gt;1.069&lt;/td&gt;
      &lt;td&gt;360&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;22x20&lt;/td&gt;
      &lt;td&gt;20x22&lt;/td&gt;
      &lt;td&gt;1.048&lt;/td&gt;
      &lt;td&gt;440&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;24x22&lt;/td&gt;
      &lt;td&gt;22x24&lt;/td&gt;
      &lt;td&gt;1.031&lt;/td&gt;
      &lt;td&gt;528&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;26x24&lt;/td&gt;
      &lt;td&gt;24x26&lt;/td&gt;
      &lt;td&gt;1.016&lt;/td&gt;
      &lt;td&gt;624&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;28x26&lt;/td&gt;
      &lt;td&gt;26x28&lt;/td&gt;
      &lt;td&gt;1.004&lt;/td&gt;
      &lt;td&gt;728&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The 28x26 solution appears to be so optimal that there appears to be no better solution up to 10000 points (i didn’t search past that point. Eventually, of course, there should be an even better approximation though it becomes intractable for the assignment algorithm (see below) since the Kuhn-Munkres algorithm is O(N^3).&lt;/p&gt;

&lt;p&gt;Ok, now once we have the grids we still have to assign which point in the square grid becomes which point in the hex grid.
We can calculate the ideal minimal distortion by solving &lt;a href=&quot;https://en.wikipedia.org/wiki/Hungarian_algorithm&quot;&gt;the assignment problem&lt;/a&gt; over a cost matrix of distances. For the patches to be tileable we need to calculate the distance matrix under periodic boundary conditions, i.e if you walk out of the unit cell to the left you reappear on the right.&lt;/p&gt;

&lt;h2 id=&quot;16x14--224-points&quot;&gt;16x14 = 224 points&lt;/h2&gt;

&lt;p&gt;&lt;a type=&quot;text/html&quot; href=&quot;/assets/squaretohex/solution.224.txt&quot;&gt;&lt;img src=&quot;/assets/squaretohex/solution.224.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;18x16--288-points&quot;&gt;18x16 = 288 points&lt;/h3&gt;

&lt;p&gt;&lt;a type=&quot;text/html&quot; href=&quot;/assets/squaretohex/solution.288.txt&quot;&gt;&lt;img src=&quot;/assets/squaretohex/solution.288.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;20x18--360-points&quot;&gt;20x18 = 360 points&lt;/h2&gt;

&lt;p&gt;&lt;a type=&quot;text/html&quot; href=&quot;/assets/squaretohex/solution.360.txt&quot;&gt;&lt;img src=&quot;/assets/squaretohex/solution.360.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;22x20--440-points&quot;&gt;22x20 = 440 points&lt;/h2&gt;

&lt;p&gt;&lt;a type=&quot;text/html&quot; href=&quot;/assets/squaretohex/solution.440.txt&quot;&gt;&lt;img src=&quot;/assets/squaretohex/solution.440.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;24x22--528-points&quot;&gt;24x22 = 528 points&lt;/h2&gt;

&lt;p&gt;&lt;a type=&quot;text/html&quot; href=&quot;/assets/squaretohex/solution.528.txt&quot;&gt;&lt;img src=&quot;/assets/squaretohex/solution.528.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;26x24--624-points&quot;&gt;26x24 = 624 points&lt;/h2&gt;

&lt;p&gt;&lt;a type=&quot;text/html&quot; href=&quot;/assets/squaretohex/solution.624.txt&quot;&gt;&lt;img src=&quot;/assets/squaretohex/solution.624.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;28x26--728-points&quot;&gt;28x26 = 728 points&lt;/h2&gt;

&lt;p&gt;&lt;a type=&quot;text/html&quot; href=&quot;/assets/squaretohex/solution.728.txt&quot;&gt;&lt;img src=&quot;/assets/squaretohex/solution.728.gif&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;tiling&quot;&gt;Tiling&lt;/h2&gt;

&lt;p&gt;All these can be tiled so arbitrarily large planes of points can be converted this way.:&lt;/p&gt;

&lt;h3 id=&quot;224-2x2&quot;&gt;224 2x2&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/squaretohex/solution.224.tile2x.gif&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;728-2x2&quot;&gt;728 2x2&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/squaretohex/solution.728.tile2x.gif&quot; /&gt;&lt;/p&gt;

</description>
        <pubDate>Sat, 11 Mar 2017 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//graphics/2017/03/11/square-to-hex.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//graphics/2017/03/11/square-to-hex.html</guid>
        
        
        <category>graphics</category>
        
      </item>
    
      <item>
        <title>3D printing glass and bronze: Lost-PLA casting</title>
        <description>&lt;p&gt;For a few years now I have been experimenting with casting glass and occasionally bronze from 3D printed positive models. Specifically I use this technique to make sculptures of protein molecules rendered as their solvent accessible surface, which are created from their scientifically precise chrystallographic coordinates.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/lysozyme.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I begin the process of casting a molecular sculpture by reviewing different possible macromolecules for their potential. I often get asked how I choose my models. I have three criteria:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scientific relevance&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Is it an interesting macromolecule?&lt;/li&gt;
  &lt;li&gt;Does it have an important function that is readily explainable and accessible?&lt;/li&gt;
  &lt;li&gt;Does it have historical significance in terms of its discovery ?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Artistic relevance&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Do I find it aesthetically pleasing ?&lt;/li&gt;
  &lt;li&gt;Is there symmetry in quaternary structure, or a regularity to its fold?&lt;/li&gt;
  &lt;li&gt;Does it have a particularly striking shape when rendered volumetrically or through ribbons ?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Practical feasibility&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Is it feasible to cast, process, mount etc. ?&lt;/li&gt;
  &lt;li&gt;Is its size prohibitive in terms of equipment I have and time expenditure.&lt;/li&gt;
  &lt;li&gt;Does it have structural features that make it unrealistically difficult ?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once I’ve chosen a candidate I build an initial model using a molecular editor such as RasMol or PyMol. PyMol’s volume function is quite good. MSMS also works. At this stage I think about the final presentation and mounting. Somehow a flat surface is needed or some sort of cradle. It’s good to have some sort of idea even this early of how it’s going to work, even though in practice I often change my mind later as I get to know the piece. For multipart pieces I also assess the fit and do other artistic edits in terms of choosing a conformation for flexible molecules (such as the carbohydrate in the lysozyme piece) which isn’t rigidly determined. There I can manipulate the torsion angles to change the overall shape in a way that makes sense. Finally I calculate a solvent accessible area and save the coordinates and faces as an .stl or .obj file.&lt;/p&gt;

&lt;p&gt;These raw models I then load into Blender for further editing. In many cases I will make changes at this stage. First I have to decide on a scale, though in practice all my current work is at the same scale of 4 Angstrom/inch. The mixing of metric and imperial units is a bit funny, but it came from when I was making hand-formed copper models where my raw materials were in imperial units but the units of molecular modeling are Angstrom or nanometers. Somehow this scale worked well for what I was doing and so it has become my standard.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/blender.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I also prepare the models for printing. Since macromolecules don’t have a flat surface I almost always cut the model into two parts, creating a large surface for each half to be printed on. Choosing the cut plane is critical to minimize the number of overhangs during printing, which usually yield worse surface quality and waste support material. In some cases I use 3 or 4 cut planes, especially when the models get large, here I have to work carefully to take in account the printing volume and balance number of pieces vs printability and overhang avoidance.&lt;/p&gt;

&lt;p&gt;I then print each part on a typical PLA extrusion printer. Even though the print is just a throwaway positive I actually print it with a relatively thick skin and a reasonably large amount of fill density. This is for a number of reasons. In post processing, especially smoothing, I found having a thin skin to be problematic. Especially during solvent smoothing the model can collapse or distort if it does not have sufficient internal support. Furthermore a thin skin is more likely to have a small hole that might go undetected. This will later lead to ingress of plaster-slurry which can end up being embedded in the glass (see section below). A thicker skin will be easier to keep watertight.
I almost always choose clear PLA, because it burns out the cleanest. Colored PLA (even that “white” variety) has additional additives which do not burn out well.
Once all the parts are printed I glue them together, usually using a small amount of superglue first to keep them in a fixed position and then I seal the seam using a soldering iron. Generally I manipulate the PLA using soldering irons which work great when small parts of the model need to be repaired, fused, remove or added. I use a little PLA filament as “solder” if I need to add material. I have a couple of different tips in different shapes which I use like hot spatulas. I use these to smooth out any coarse artifacts on the model. Fine stuff is left for the next step.
At this stage I also make sure different models fit together properly and I make any necessary modifications if not.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/model_1.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To get rid of the print lines I’ve tried a number of different techniques, many of which didn’t really work. Obviously getting the highest possible print to start with is paramount. Thinner layer thicknesses are better and make smoothing much easier. Smoothing is important though. The plaster will readily pick up the fine detail of the print lines and those will later be evident int he glass/metal. Now is the easiest time to get rid of them.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/print_lines.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Generally these days I start by brushing on some Dichloromethane (DCM, or methylene chloride). This stuff is really very nasty and I do this work either in a fume hood or outside with a fan blowing away from me. I use a respirator with chemical filters, &lt;a href=&quot;http://amo-csd.lbl.gov/downloads/Chemical%20Resistance%20of%20Gloves.pdf&quot;&gt;PVA gloves&lt;/a&gt; and full eye glasses. Do not use this chemical unless you’ve taken the proper safety precautions!
However I found no other solvent even remotely as good at smoothing as Dichloromethane. Apparentl Tetrahydrofuran (THF) is ok, though I’ve not tried it. Acetone is completely useless, as PLA is completely insoluble in Acetone. Due to the nastiness of Dichloromethane I’ve never attempted doing vapor smoothing, but the simple cold brush on works surprisingly great. Sometimes I do two “coats”.
With each coat the Dichloromethane dissolves the surface layer of the PLA and the resolidifies, running into the cracks. It also conveniently seals any tiny holes in the print where print layers have failed to fuse properly.
Another method is to spray on many coats of a lacquer, e.g. shellac. This has some problems though during burnout and can leave undesirable carbon residues in the mold which can be tricky to remove. With higher burnout temperatures though this has been less of a problem.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/carbon_residue.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One thing to be aware of though is that if you ever intend to make a rubber mold out of a printed positive, do not use Shellac. Shellac has residual alcohol in it and apparently is one of the very very few things that bonds to setting rubber. I found this out the hard way when my mold positive didn’t want to come out of the mold, cracking the rubber. For direct-into-plaster positives it works ok though thick layers have a tendency to char and leave carbon deposits.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/editing_the_mold.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When the model is ready and smooth, I attach the sprues and air vents. Depending on whether it is a metal cast or a glass cast these have to be designed differently. Liquid metal has a very low viscosity and runs fast. This means that supply channels can be more narrow but air vents have to be a little wider since the air is getting displaced much faster (almost instantly). Metal will also run up channels so not every part of the model has to have a supply line “from the top” so to speak. Often a single inlet is sufficient. In fact often a better quality is achieved when the cavity is filled from the bottom.
Glass on the other hand is a completely different animal. It oozes, slowly, over the course of many hours. Much thicker than honey. This means supply channels want to be relatively wide (or it will take a very long time). Air vents can be very thin since the air has tons of time to escape.
Also glass won’t run upwards very much. On the bottom of the model, with sufficiently long melts, I found that the pressure is high enough to force glass upwards perhaps an inch or two at best. Higher Up on the model though it won’t, so each chamber of the model needs at least a small supply sprue. The size is determined by the size of the chamber to be filled, though I often er on the side of caution. Believe me it is a crushing feeling when you open your mold and everything went well except some little part of it didn’t fill.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/didnt_fill.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The other thing I check at this stage is water tightness. The easiest way is just to submerge the entire model in a bucket of water and watch for any bubbles, just like you would with a bicycle tire. If it bubbles, you have a problem. It turns out that when the model is leaky, a little plaster slurry will seep into the internal honeycomb of the printed model. It hardens and when you burn out the plastic will remain in this crumbly but solid lattice shape, while the surrounding plastic evaporates. The remaining plaster will stay behind and end up getting embedded into the glass. Another disappointing thing to find out after everything else worked seamlessly. This is also a great time to estimate the volume of the positive. This will later inform how much glass to place in the kiln, which has to be done relatively accurately.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/internal_plaster_leak.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Do make air vents I simply use printer filament. To attach them I use a soldering iron. For glass the air vents don’t need to go to the top of the mold, they can go to the bottom or the side. The glass is too viscous to leave through the air vents. With metal they need to go to the top. Liquid metal can squeeze through tiny holes and channels and you risk a leak.
I’ll often connect the air vents into a sort of network both to make it more stable (it’s easy to break it off when handling the model) and to create redundancy. Below is a photo of a fully set up model, ready for pouring of the plaster mix. Note it’s upside down. The inlet, the main sprue, is on the bottom and is connected to a coated wooden board using plasticine or wax.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/model_ready.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To make the actual mold I’ve tried two different ways, both work and it sort of depends on gut feeling and the shape of the piece as to which I’ll choose.
The first involves building a wall around the piece that’s stuck to the base plate. I often use corrugated plastic to make the barrier. I use wax and/or plasticine to seal the bottom of the wall. Since the piece is upside down, the wall needs to be about 1-2 inches taller than the highest point of the mold such that the mold later has a thick enough bottom.
The second way involves taking a suitably sized cardboard box and a plastic bag. I line the inside of the cardboard with the bag, taping it to the side. Later I will fill the “bucket” with plaster and then lower the piece down into it from the top, hanging from the support board. In this case I usually use a long slender support so that I have space to add additional plaster if needed.&lt;/p&gt;

&lt;p&gt;Time to pour the plaster mix. For glass casting the mold mix is 1:1:1 silica:plaster:water, by weight. The silica is 200 mesh finely ground silica. It acts as a refractory material, i.e. it helps withstand the heat. The plaster is simple potter’s plaster. I thoroughly mix these at 1:1 as dry powders first. This ensures very thorough mixing later so there aren’t pockets of just plaster or just silica. To mix the plaster you always add the powder mix to a bucket of water. I guesstimate the final amount I need for the mold. You need a little over half the final volume in water. Perhaps 60% or so. Better too much than too little, nothing more annoying than having to mix another batch in a rush. Eyeballing the amount of plaster needed I found fiendishly difficult. Either way, you start with a bucket of water and slowly add the powder mix until a dry island appears at the top of the water surface, which doesn’t sink in anymore. It’s important not to stir during that time. Usually by the time the last of the plaster mix is added there’s a little island in the middle of the bucket surrounded by water. I usually wait a minute and let more of the air escape and the plaster soak through. Then, if it’s a small amount I mix it with my hands or in case of larger amount, I use a drill with a mixer attachment. Avoid whisking in air, this is the opposite of pancake making.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/face_coat.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For many molds I will first do a face coat and then finish the mold in second pour. The first coat I pour on, and work into all the details using a brush, ladling it on and on until it becomes thick enough to stick, forming a thickish layer over the positive. This step ensures every crevice is filled and also reduces the amount of surface bubbles since working in the material with your hands or a brush tends to pop any bubbles or promote them rising to the surface. As the plaster hardens I rake in some grooves with my fingers to provide a good interlocking with the second, poured, layer. For most molds I set in some fiberglass around the outside and sometimes additional fiberglass ribbon if the mold is particularly slender. There is nothing worse than a cracked, burst mold with oozing glass emerging spilling in the kiln.
I then mix a second batch of plaster and pour it in. Generally I do both layers in one sitting. You don’t want the face coat to dry out and then pour the second one.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/second_pour.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now that the mold is poured, I generally let it air dry for a few days. After the first 24 hrs I take off the box or container that held the plaster, aiding drying. 
Next, we need to burn out the mold. I place the mold, upside-down, in the kiln, usually on top of a metal grating I’ve built. Underneath I place a metal pan to catch the drippings. The pan is lined with some aluminum foil for easy cleaning. The burnout protocol plateaus at several temperatures. First 230 to dry the mold completely. Then at 500 to drive off all the chemically bound water and start melting the PLA. At the end of this stage the bulk of the PLA will have melted and dripped out into the pan, so I will open the kiln and empty the pan. The program then ascends to 700. At this temperature PLA will evaporate rapidly and carbon deposits will also oxidize away. I noticed that when I didn’t go up to this temperature, some of the molten PLA mould soak into the plaster and then, upon cooling and contraction, cause the plaster to crack. However if the temperature is raised sufficiently, the PLA evaporates completely, thus not creating those stresses during cooling and many cracks are avoided. Some cracks still occur, but the resultant flashing can later be ground off.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Time (Hr:Min)&lt;/th&gt;
      &lt;th&gt;Temp(F)&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;2:00&lt;/td&gt;
      &lt;td&gt;230&lt;/td&gt;
      &lt;td&gt;Drying wet mold&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2:30 (Hold)&lt;/td&gt;
      &lt;td&gt;230&lt;/td&gt;
      &lt;td&gt;Drying wet mold&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3:00&lt;/td&gt;
      &lt;td&gt;500&lt;/td&gt;
      &lt;td&gt;Melt PLA without burning it&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4:00 (Hold)&lt;/td&gt;
      &lt;td&gt;500&lt;/td&gt;
      &lt;td&gt;Melt PLA without burning it&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2:00&lt;/td&gt;
      &lt;td&gt;700&lt;/td&gt;
      &lt;td&gt;Vaporize remaining PLA&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4:00 (Hold)&lt;/td&gt;
      &lt;td&gt;700&lt;/td&gt;
      &lt;td&gt;Vaporize remaining PLA&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7:00&lt;/td&gt;
      &lt;td&gt;60&lt;/td&gt;
      &lt;td&gt;Cool to room temperature&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/bricking_in_the_mold.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At this point the mold is ready for casting. With the opening pointing upwards, the mold is placed in the kiln. I often place fire bricks around the mold just in case it fails the bricks will keep the mold from completely breaking apart and leaking its contents into the kiln. Cleaning off the glass from the shelf and heating elements is a huge pain. Above the mold I place a suitable size flower pot which will hold most of the cold glass.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/bricking_in_the_mold_2.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Using my earlier estimate of the volume I figure out how much glass to put in the flowerpot. Ideally the amount is just right so that at the end of the melt the mold is completely full, with a good amount remaining in the funnel above the mold but virtually none in the flowerpot. Too much glass and it will overflow the mold, waste glass and cause a mess. Also it will shatter during demolding and, if unlucky, will send cracks propagating into the piece. Too little and the piece won’t be filled completely. Either outcome is highly undesirable.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/spilled_glass.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Sometime the geometry of the piece doesn’t allow for all of the necessary glass to be placed in the pot at once. In that case some has to be reserved and added during the melt. My kiln has a special hole at the top to allow this to happen easily. However adding cold glass to a 1500F kiln causes the glass to violently shatter, sometimes spraying bits of it everywhere in the kiln, once again, making a mess (or potentially falling into another mold if multiple pieces are being cast at once). Measuring the volume of glass chunks is easiest done also by adding them to a bucket of clean water and measuring the displacement which can be compared to the displacement of the positive, done earlier.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/coldglass.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I often load the glass wet (but not dripping) into the flowerpot - as the kiln warms up there’s plenty of time for it to evaporate. I buy the glass in billets, usually from Gaffer. To get the right amount I use a glass cutter to score the billets and a hammer to crack them (wear safety glasses!). I try and minimize the number of pieces, since the smaller the pieces the more likely they are to trap bubbles as they melt. Bigger bubble-free pieces are best, but sometimes fitting it all into the flowerpot necessitates breaking them up a bit more than ideal.&lt;/p&gt;

&lt;p&gt;The next step is figure out the firing protocol. These are given usually by the glass manufacturer (e.g. &lt;a href=&quot;http://www.gafferglass.com/technical/annealing-schedule-for-casting-crystal/&quot;&gt;Gaffer&lt;/a&gt;) and depend on the thickness of the thickest part of the model and
how thick the mold is. An example profile is below.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Time (Hr:Min)&lt;/th&gt;
      &lt;th&gt;Temp(F)&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;3:00&lt;/td&gt;
      &lt;td&gt;300&lt;/td&gt;
      &lt;td&gt;Drying wet mold&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2:00 (Hold)&lt;/td&gt;
      &lt;td&gt;300&lt;/td&gt;
      &lt;td&gt;Drive off chemical water&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6:00&lt;/td&gt;
      &lt;td&gt;1000&lt;/td&gt;
      &lt;td&gt;Heating glass and mold&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2:00 (Hold)&lt;/td&gt;
      &lt;td&gt;1000&lt;/td&gt;
      &lt;td&gt;Time to equalize temperature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10&lt;/td&gt;
      &lt;td&gt;1475&lt;/td&gt;
      &lt;td&gt;Bring up to melting temp&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8:00 (Hold)&lt;/td&gt;
      &lt;td&gt;1475&lt;/td&gt;
      &lt;td&gt;Allow glass to flow&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2:00&lt;/td&gt;
      &lt;td&gt;890&lt;/td&gt;
      &lt;td&gt;Crash temp to solidify piece&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10:00 (Hold)&lt;/td&gt;
      &lt;td&gt;890&lt;/td&gt;
      &lt;td&gt;Hold at Annealing temperature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;20:00&lt;/td&gt;
      &lt;td&gt;700&lt;/td&gt;
      &lt;td&gt;Slow cooling&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;20:00&lt;/td&gt;
      &lt;td&gt;300&lt;/td&gt;
      &lt;td&gt;…&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6:00&lt;/td&gt;
      &lt;td&gt;75&lt;/td&gt;
      &lt;td&gt;…&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;I usually skip the first two steps if the mold is already dried and burned out.
Be sure to check with your glass’ manufacturer about the right protocol, it depends strongly on the type of 
piece and the glass chemistry.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/controller.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The melt time depends also on the geometry of the model and how the glass flows. There needs to be enough time for the glass to reach every part of the mold. The thinner parts of the mold are the more time that takes, however it depends also on other factors. The pressure at the bottom of the mold is much larger than at the top so glass will flow faster, generally. There is no hard science here, just experience and guessing. I usually err on the side of longer, just to be sure. However longer also means the mold spends more time at the highest temperature which weakens the plaster and stresses the mold.
I also plan to check the kiln 30 minutes or so before the temperature is scheduled to drop again. It is relatively safe to open the kiln, since the glass is molten and can’t suffer thermal cold shock. However you are staring into a 1500F hell hole so protective gloves and a face shield are a must. The infrared radiation is super intense. I always try and place the mold in such a way that I can see if the mold is full even if the door is just slightly cracked to minimize exposure (Though at least on one occasion I had to fix a problem inside the kiln at top temperature and had to reach in with kevlar gloves and manipulate a fallen flowerpot - I don’t recommend it). If after the inspection the mold isn’t full I can extend the melt time and, if required, add more glass.
Once the program moves into its annealing phase I try not to open the kiln anymore until the program is completely done. There is literally nothing to be done, the glass is solid anyway and opening it risks cold shock and cracks.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/demolding_1.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After the cast is completely cold (i usually wait 24hrs after the program’s end) I begin removing the mold material by this point it is quite brittle and it’s easy to break it. If the cast shape is quite convex and thick, I usually just remove the mold dry by gently chiseling (with a wooden chisel) and knock on the plaster until it falls of.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/demolding_2.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;However if the mold is quite fragile or complex it is easy to break the glass at this point. To demold I soak the entire mold in a bucket of water. after a few hours the plaster will fall off almost by itself. I then clean the piece with water.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/demolding_4.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The next step is cold working. Using a dremel and a diamond bit I grind off the air vents, the sprues and any flashing. Once completed, wash the piece again and then let it dry for sandblasting. The sandblasting evens out the texture between the places the piece has been ground and other spots, giving a smooth even finish. It can also help reduce any print lines that are still visible.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/coldworking.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Sandblasting smoothes the glass but leaves a very white, scattering surface. Furthermore any moisture or oils from fingers will reduce light scattering and leave dark prints. To make the glass more translucent again it is then etched using a biflouride salt (like potassium biflouride). It is available in powder form from “His Glassworks” - again this is a quite hazardous substance. I work in a well ventilated environment with safety glasses and rubber gloves. 
The powder is simply dissolved in water and the glass is placed in the solution for 10 minutes or so. Afterwards i dip the glass in a baking soda solution to neutralize the acid. The etching process leaves a white film behind (likely some insoluble compound of the silica and the acid). This needs to be scrubbed off. On of the easiest ways I found to do this is to use a pressure washer. I place the glass on the lawn outside, on an old towel and direct the pressure washer stream down wards onto the glass. This is quite effective removing the white deposits, even in hard to access crevices and pits.
Obviously if the glass is quite thin and fragile using the pressure washer method is dangerous and may crack the glass. In that case using a stiff brush, like a toothbrush makes more sense.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/casting/lysozyme.jpg&quot; /&gt;
&lt;img src=&quot;http://www.miketyka.com/projects/kiss/img1.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To see more pieces check out &lt;a href=&quot;http://www.miketyka.com/&quot;&gt;my website&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Sun, 11 Dec 2016 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//art/2016/12/11/lostpla-casting-glass.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//art/2016/12/11/lostpla-casting-glass.html</guid>
        
        
        <category>art</category>
        
      </item>
    
      <item>
        <title>Class visualization with bilateral filters</title>
        <description>&lt;p&gt;A while ago I played with style visualizations and bilateral filters. The latter have the nice property of filtering out noise but preserving edges. Here are some example class from GoogLeNet (Inception network). Big shout out to &lt;a href=&quot;http://auduno.com/post/125362849838/visualizing-googlenet-classes&quot;&gt;Audun m. Øygard&lt;/a&gt; and &lt;a href=&quot;https://github.com/kylemcdonald/deepdream/blob/master/dream.ipynb&quot;&gt;Kyle McDonald&lt;/a&gt; who were among the first to use filters (e.g. gaussian blurs) essentially as image regularizers for single class visualizations. These visualizations here were directly inspired by their ideas.&lt;/p&gt;

&lt;h1 id=&quot;neural-network-art-show&quot;&gt;Neural Network art show&lt;/h1&gt;

&lt;p&gt;I’ll be showing 3 high-res pieces based on this technique at the &lt;a href=&quot;http://grayarea.org/event/deepdream-the-art-of-neural-networks/&quot;&gt;Neural Network art show at Gray Area in San Francisco&lt;/a&gt; end of February.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/dark_0034_09.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Class #34, Leatherback Turtle&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/dark_0076_09.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Class #76, Tarantula&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/dark_0144_09.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Class #144, Pelican&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/dark_0156_09.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Class #156, Blenheim Spaniel&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/dark_0488_09.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Class #488, Chain&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/dark_0725_09.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Class #725, Pitcher&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/dark_0944_09.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Class #944, Artichoke&lt;/p&gt;

&lt;h2 id=&quot;fun-with-video&quot;&gt;Fun with video&lt;/h2&gt;

&lt;p&gt;Here’s a little animation I call the “Animal parade”&lt;/p&gt;

&lt;iframe width=&quot;640&quot; height=&quot;480&quot; src=&quot;https://www.youtube.com/embed/2GDpJuvZsdY&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;combining-bilateral-filters-with-style-transfer&quot;&gt;Combining bilateral filters with style transfer&lt;/h2&gt;

&lt;p&gt;These were made using bilateral class visualization using GoogLeNet (cello, violin, saxophone) and then had style transfer applied with the style coming from a natural picture (photo).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/instrument1.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/instrument2.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/classviz/instrument3.jpg&quot; /&gt;&lt;/p&gt;

</description>
        <pubDate>Fri, 05 Feb 2016 00:00:00 +0000</pubDate>
        <link>http://mtyka.github.io//deepdream/2016/02/05/bilateral-class-vis.html</link>
        <guid isPermaLink="true">http://mtyka.github.io//deepdream/2016/02/05/bilateral-class-vis.html</guid>
        
        
        <category>deepdream</category>
        
      </item>
    
  </channel>
</rss>
