<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Mayo Nesso on Medium]]></title>
        <description><![CDATA[Stories by Mayo Nesso on Medium]]></description>
        <link>https://medium.com/@mayo-nesso?source=rss-9d59c510be07------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*OQQb5jdm0dazo4pNhJrE1w@2x.jpeg</url>
            <title>Stories by Mayo Nesso on Medium</title>
            <link>https://medium.com/@mayo-nesso?source=rss-9d59c510be07------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 02:25:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@mayo-nesso/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[[LiP] Metaballs in Unity URP with Custom Render Passes]]></title>
            <link>https://medium.com/@mayo-nesso/lip-metaballs-in-unity-urp-with-custom-render-passes-22fa1dc07fde?source=rss-9d59c510be07------2</link>
            <guid isPermaLink="false">https://medium.com/p/22fa1dc07fde</guid>
            <category><![CDATA[unity]]></category>
            <category><![CDATA[unity-urp]]></category>
            <category><![CDATA[metaballs]]></category>
            <category><![CDATA[universal-render-pipeline]]></category>
            <dc:creator><![CDATA[Mayo Nesso]]></dc:creator>
            <pubDate>Sun, 13 Oct 2024 11:27:42 GMT</pubDate>
            <atom:updated>2024-10-13T11:36:07.848Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GylHYqKOgAKigMN0qMZ99A.jpeg" /><figcaption>Photo by <a href="http://www.instagram.com/thebigmayowski">thebigmayowski</a></figcaption></figure><h4>TLDR</h4><p>[<a href="https://github.com/mayo-nesso/urp-metaballs">Source code</a>] — [<a href="https://mayonesso.com/blog/2024/09/metaballs/">Unwalled article</a>]</p><h4>Introduction</h4><p>Metaballs! Yeah, like meatballs, but no meat.</p><p>Before jumping into that, I recall, years ago, when a colleague at the office gave a small talk about Fragment Shaders.<br>At that time, I thought I’d be better off deciphering the Necronomicon (including searching for it in the local library).</p><p>Today, my almost absurd and preferred simplification to explain the concept is that it’s just a function that returns 4 values, which turn out to be the color of the pixel to be drawn (Red, Green, Blue, and Alpha).</p><p>Of course, this is a partial simplification of something more complex than that, but why scare innocent people too early?<br>All this fits within the world of ‘how to draw beautiful things on the screen’.<br>And this post is precisely about that.</p><p>Well, maybe not so beautiful things, but some basic attempts.</p><h4>Metaballs: What are they?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/0*bQB2DIHV_xTHvBUS" /></figure><p>This brings us back to Metaballs: What are they? The simplest and most direct way is: ‘Do you remember those lava lamps?’</p><p>Those viscous balls that would join and then separate in a somewhat animal-like way?</p><p>Well, Metaballs are about that effect, and this article, like many others, is about how to achieve it.</p><h4>How to ‘achieve’ the effect</h4><p>Simplifying, I’ve seen two approaches out there on how to do it.<br>The mathematical way, and the slightly trickier way.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/390/0*j8KUZXE6dsJxsoMs" /><figcaption>Math what?</figcaption></figure><p>The first approach, more elegant, follows a technique called “Marching Cubes”.<br>It was published in 1987 in a paper called “Marching Cubes: A High Resolution 3D Surface Construction Algorithm”.</p><p>In a simplified view, it uses information from circumferences and the vertices of a grid of cubes in space to draw.<br>Draw what? Well, whatever needs to be drawn.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xC3VgydVtirLX6sW" /><figcaption><a href="https://en.wikipedia.org/wiki/Marching_squares">Marching Squares (2D version)</a></figcaption></figure><p>I know that it sounds strange, but let’s say on one side we have a grid with sensors at each vertex, and on the other side our balls.<br>(Plus, a ball can be over multiple sensors).</p><p>Whenever a ball is over a sensor, the sensor will be <em>active</em>, then, taking a set of sensors, we will draw lines according to which sensors are active in that set. And so on until we complete our entire grid.</p><p>Easier to say than done?<br><a href="https://jurasic.dev/marching_squares/">This is an excellent resource to get a grasp of it</a><br>(this is the 2D version, squares instead of cubes)</p><p>In the second approach, slightly more ingenious tricks are used to calculate or compose the effect of ‘closeness’ or ‘influence’, and according to this, we draw!</p><p>Specifically, we can use elements that have an opaque color with a semi-transparent gradient border.<br>Now we can use the alpha values (which indicate how opaque or transparent the color is) to achieve the the final effect.<br>So, in a post-drawn process, we define some alpha thresholds to draw our Metaballs.<br>We can define more than one level, so we can have one color for the body and another for the border.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*rbXYsJlcqTWu9nmG" /></figure><p>In the case of a single element, there’s nothing very interesting to see.<br>But when two bodies get close, their respective semi-transparent borders will join forces to ‘saturate’ the alpha channel until they reach the threshold that indicates that we have to draw <em>something</em> on the screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/0*RCFmjuY4ZiGlZEwO" /></figure><p>In summary;</p><ul><li>Marching cubes: 1987 paper called ‘Marching Cubes: A High Resolution 3D Surface Construction Algorithm’</li><li>Calculate or draw closeness/influence, paint body and contours according to it</li></ul><h4>Some implementations / Tutorials</h4><p>In the first approach:</p><ul><li>1.a. <a href="https://jamie-wong.com/2014/08/19/metaballs-and-marching-squares/">This tutorial of Jamie Wong is quite clear and illustrative</a><br>Note that this technique works for 3D and also for 2D, hence the name “Marching Cubes” or “Marching Squares”.</li></ul><p>In the second category, I share with you these four implementations;</p><ul><li>2.a. <a href="https://danielilett.com/2020-03-28-tut5-2-urp-metaballs/">A cool tutorial from Daniel Lilett</a> where he uses the metaball positions and radius to calculate the ‘closeness’ and to draw the ‘area of influence’; then ‘apply the colors according to it’ on a render pass</li><li>2.b. <a href="https://patomkin.com/blog/metaball-tutorial/">A smart and tricky approach where Artjoms Neimanis</a> uses the Blur effect to obtain the ‘alpha’ channel, overlapping balls will saturate the ‘area of influence’ and an extra camera applies the colors according to the rules on a shader in a dedicated render texture</li><li>2.c. <a href="https://github.com/HuvaaKoodia/2DMetaballs">An evolved version of the previous one by HuvaaKoodia</a> here we avoid the use of an extra camera and the render texture, applying the effect on a render pass</li><li>2.d. <a href="https://bronsonzgeb.com/index.php/2021/03/20/pseudo-metaballs-with-scriptable-renderer-features-in-unitys-urp/">And another tutorial from Bronson Zgeb</a> similar to the previous one, Blur + URP render pass!</li></ul><h4>So, why another one?</h4><p>With Marching, there are some limitations, you can’t just put different colors on the edges, just ‘contours’ (Although with some work I think it could be done, for instance; having a second system that would draw a second pass with a more adjusted ‘contour’ level?).</p><p>On the others, I would like to be able to avoid blur or to have to pass information about the location of each metaball on each frame.</p><p>And that’s the reason for this post; here I’m not coming to sell but to give away.</p><p>My idea is to remove the blur, using the alpha trick (which we will achieve with a shader) and then use a render pass with the coloring rules using another shader, similar to the last 2 posts, but this time we will use RenderGraph, which according to the Unity people, is what’s in vogue these days…</p><h4>Why does the blur bother me?</h4><p>The blur effect is calculated by copying, moving, and overlaying a semi-transparent version of the texture, giving the desired effect.<br>And this is done over multiple ‘passes’.<br>On low-end devices, this can negatively affect your frame rate (especially if you’re doing a blur on every frame!), so it’s sometimes good to look for alternatives.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1ALPQ1KM1zkwmCt8" /></figure><h4>So, what do we need?</h4><ul><li>Unity 6</li><li>A Universal Render Project (Since we’re in Unity6, the URP version is 17.03)</li></ul><h4>First step: Shader for Circles</h4><p>Instead of blur, we are going to use a shader to draw our Metaballs with an opaque body and a gradual semi-transparent border (more opaque to the center, more transparent to the ‘outside’).</p><p>The idea is to define which part of our circle will be solid and which part will be our ‘area of influence’, that is, when two balls are close, their areas of influence will be added to make the linking effect appear.</p><p>For this, we have the following code. Pretty simple, but it works.</p><pre>Shader &quot;Custom/GradientCircle&quot;<br>{<br>    Properties<br>    {<br>        _Color (&quot;Color&quot;, Color) = (1.0, 1.0, 1.0, 1)<br>        _Radius (&quot;Radius&quot;, Range(0, 1)) = 1.0<br>        _Smoothness (&quot;Smoothness&quot;, Range(0, 1)) = 0.8<br>    }<br>    <br>    SubShader<br>    {<br>        Tags {<br>            &quot;RenderType&quot;=&quot;Transparent&quot; <br>            &quot;RenderPipeline&quot;=&quot;UniversalPipeline&quot; <br>            &quot;Queue&quot;=&quot;Transparent&quot;<br>        }<br>        LOD 100<br>        Blend SrcAlpha OneMinusSrcAlpha<br>        ZWrite Off<br>        <br>        Pass<br>        {<br>            HLSLPROGRAM<br>            #pragma vertex vert<br>            #pragma fragment frag<br>            #include &quot;Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl&quot;<br>            <br>            struct Attributes<br>            {<br>                float4 positionOS : POSITION;<br>                float2 uv : TEXCOORD0;<br>            };<br>            struct Varyings<br>            {<br>                float2 uv : TEXCOORD0;<br>                float4 positionCS : SV_POSITION;<br>            };<br><br>            CBUFFER_START(UnityPerMaterial)<br>                float4 _MainTex_ST;<br>                float4 _Color;<br>                float _Radius;<br>                float _Smoothness;<br>            CBUFFER_END<br><br>            Varyings vert(Attributes IN)<br>            {<br>                Varyings OUT;<br>                OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);<br>                OUT.uv = IN.uv;<br>                return OUT;<br>            }<br><br>            half4 frag(Varyings IN) : SV_Target<br>            {<br>                // Normalize UV coordinates to range from -1 to 1<br>                const float2 uv = IN.uv * 2.0 - 1.0;<br>                // Circle center (in normalized coordinates)<br>                const float2 center = float2(0.0, 0.0);<br>                // Distance from current point (uv) to circle center<br>                const float dist = length(uv - center);<br>                // Smooth gradient: 1.0 at center, 0.0 at edge<br>                // The smoothstep function produces a smooth transition between 0.0 and 1.0<br>                // based on the distance from the circle center and the defined radius and smoothness<br>                const float smooth_circle = smoothstep(_Radius, _Radius - _Smoothness, dist);<br>                // The final alpha value is the smooth gradient value<br>                half4 color = _Color;<br>                color.a *= smooth_circle;<br>                <br>                return color;<br>            }<br>            ENDHLSL<br>        }<br>    }<br>}</pre><p>We create a new material with this shader and, voilà:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*TEs0oeNbNjuDe9m5" /><figcaption>We have a nice radial gradient</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/824/0*9MlPzF3LFZYyt7Hz" /><figcaption><strong>Radius</strong> and <strong>Smoothness</strong> are some parameters that we can adjust,<br>we also have <strong>Color</strong> but it is not that useful</figcaption></figure><h4>Second step: URP Custom Pass | Render Feature</h4><p>Once we have all the spheres on the screen, we are ready to draw the effect.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*S4gtWdhtUoSO8_qR" /></figure><p>To do this, we will create a second shader and a custom render pass. Well, strictly speaking, two passes.</p><p>This second shader will draw depending on the amount of alpha present, in this way, we can paint the body of the Metaballs and the border.</p><p>This time, we will use <em>Shader Graph</em>, and we are going to call it MetaEffect:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mlWXzu4zKpIviO_q" /><figcaption><em>…click me!</em></figcaption></figure><p>With these settings (The important part: Material = Fullscreen):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/706/0*5MqSLCGGlNnAUM0P" /></figure><p>And with the following properties:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/454/0*oHislOMFb14t4AtF" /></figure><p>The Border Color and Body Color parameters represent the respective colors of the border and the body.<br>The Min Alpha Threshold defines the minimum alpha value that will be considered as part of the border, while the Body Threshold specifies the minimum alpha value that will be classified as part of the body.<br>Any alpha value that falls between the Min Alpha Threshold and the Body Threshold will be regarded as part of the border.</p><p>Now, for our custom render pass!</p><p>In the first pass, we draw our semi-transparent balls on a temporary texture.</p><p>There are some caveats though.<br>On one hand, we don’t want to show our original balls, just the effect of it.<br>And we don’t want to apply this to everything that is on the screen, only to our balls.</p><p>To do this, in our render pass we will work only with a specific layer, so we will create a FilteringSettings to only select a specific layer during the process:</p><pre>// Settings to filter which renderers should be drawn<br>private readonly FilteringSettings _filterSettings = default;<br><br>...<br>_filterSettings = new FilteringSettings(renderQueueRange, layerMask);<br>...<br><br>private void InitRendererLists(ContextContainer frameData, ref LayerRenderPassData layerRenderPassData, RenderGraph renderGraph)<br>{<br>    // Access the relevant frame data from the Universal Render Pipeline<br>    var universalRenderingData = frameData.Get&lt;UniversalRenderingData&gt;();<br>    var cameraData = frameData.Get&lt;UniversalCameraData&gt;();<br>    var lightData = frameData.Get&lt;UniversalLightData&gt;();<br>    var sortingCriteria = cameraData.defaultOpaqueSortFlags;<br>    <br>    // Create drawing settings based on the shader tags and frame data<br>    var drawSettings = RenderingUtils.CreateDrawingSettings(_shaderTagIdList, universalRenderingData, cameraData, lightData, sortingCriteria);<br>    <br>    // Create renderer list parameters,<br>    // here we are using _filterSettings, and that is where we specified the layerMask to use<br>    var param = new RendererListParams(universalRenderingData.cullResults, drawSettings, _filterSettings);<br>    <br>    // Finally create a RenderListHandle <br>    layerRenderPassData.RendererListHandle = renderGraph.CreateRendererList(param);<br>}</pre><p>Then our first pass would look like this:</p><pre>private static void ExecuteLayerRenderPass(LayerRenderPassData data, RasterGraphContext context)<br>{<br>    // Draw all renderers in the list<br>    context.cmd.DrawRendererList(data.RendererListHandle);<br>}<br><br>...<br><br>// Set up the layer render pass<br>const string layerRenderPassName = &quot;Mat2Layer: Layer Render 1/2&quot;;<br>using (var builder = renderGraph.AddRasterRenderPass&lt;LayerRenderPassData&gt;(layerRenderPassName, out var passData))<br>{<br>    InitRendererLists(frameData, ref passData, renderGraph);<br>    builder.UseRendererList(passData.RendererListHandle);<br>    <br>    // Set up texture dependencies<br>    // We are not really using &#39;srcCamColor&#39; on this pass,<br>    // but we are going to keep the next line for clarity and documentation... <br>    builder.UseTexture(srcCamColor); <br>    builder.SetRenderAttachment(temporaryHandle, 0);<br>    builder.SetRenderAttachmentDepth(srcCamDepth);<br>    <br>    builder.SetRenderFunc((LayerRenderPassData data, RasterGraphContext context) =&gt; ExecuteLayerRenderPass(data, context));<br>}</pre><blockquote><em>Here is worth note that we follow the structure of:<br>a.- setting a source (with </em><em>builder.UseTexture(srcCamColor)),<br>b.- setting the destination (with </em><em>SetRenderAttachment(temporaryHandle, 0) and </em><em>SetRenderAttachmentDepth(srcCamDepth)),<br>c.- doing some work (with </em><em>builder.SetRenderFunc)</em></blockquote><blockquote><em>But, as you can see in the comments, we are not really using </em><em>srcCamColor (ie; ‘what is rendered in the camera so far’), instead we are instructing our pass to use our </em><em>FilteringSettings defined onto </em><em>RendererListHandle, in this line: </em><em>builder.UseRendererList(passData.RendererListHandle);</em></blockquote><p>In the second pass, we apply the second shader to what was drawn in the previous pass (stored in our temporary texture handle called temporaryHandle) with BlitTexture.<br>And we draw the result on the screen.</p><p>Something like this:</p><pre>private static void ExecuteBlitPass(BlitPassData data, RasterGraphContext context)<br>{<br>    // Blit the source texture to the current render target using the specified material<br>    Blitter.BlitTexture(context.cmd, data.Source, ScaleBias, data.Material, 0);<br>}<br>...<br><br>// Set up the blit pass<br>const string blitPassName = &quot;Mat2Layer: Blit Pass 2/2&quot;;<br>using (var builder = renderGraph.AddRasterRenderPass&lt;BlitPassData&gt;(blitPassName, out var passData))<br>{<br>    // Configure pass data<br>    passData.Material = _material;<br>    // Use the output of the previous pass as the input<br>    passData.Source = temporaryHandle;<br>    builder.UseTexture(passData.Source);<br>    <br>    // Set the render target to the original color buffer<br>    builder.SetRenderAttachment(srcCamColor, 0);<br>    builder.SetRenderAttachmentDepth(srcCamDepth);<br>    <br>    builder.SetRenderFunc((BlitPassData data, RasterGraphContext context) =&gt; ExecuteBlitPass(data, context));<br>}</pre><blockquote><em>All the details are in </em><a href="https://github.com/mayo-nesso/urp-metaballs/blob/main/Assets/Scripts/MaterialToLayerRenderPass.cs"><em>MaterialToLayerRenderPass.cs</em></a><em> take it a look, there are a lot of comments!</em></blockquote><p>What is left is to create a new Layer, and assign our balls to it.<br>Then, we will unselect that Layer from the rendering process (since we are going to take care of the drawing itself)</p><h4>Unity Scene</h4><p>In our scene, we will start with a plane to which we will apply the material that we made from our GradientCircle shader.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/912/0*v2LgOCqylTNpHXM3" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/0*QqWsed2RU6HCXYVW" /></figure><p>Then, we will place these elements on a new Layer called Metaballs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/582/0*FxMcz6dJwljz_cu3" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/734/0*WDQ9s4fmGe02LULc" /></figure><p>On the other hand, we will have our UniversalRendererData (We can start with PC_Renderer, but remember that if we’re going to use this on mobile, then we’ll have to modify MobileRenderer) here we will first deselect the Metaballs layer, with this, the camera will not draw our balls on the screen!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/758/0*pHz6tgW9Kte3YOLT" /></figure><p>After that, we add a new render feature; ours!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/754/0*mJT4MZSDEg_GqDqr" /></figure><p>So we start looking for it:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/498/0*4owxvNZnqCIKs-Tk" /></figure><p>And we add it, and configure it!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/766/0*LAm1aGMrh1yHy6l2" /></figure><p>We configure it by telling it that:</p><ul><li>Render Queue = All</li><li>LayerMask = Metaballs</li><li>Material = MetaEffect</li><li>RenderPassEvent = After Rendering SkyBox.</li></ul><h4>Results</h4><p>So, with one Metaball we should see this;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/460/0*32J1dV7Ol-JQuzEw" /></figure><p>But, more of them;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wH_9D9Crt_Ld3A1v" /></figure><p>And if we go to the Frame Debugger we will see what is happening behind the curtains:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*C4vOpLQdqPK-gms0" /><figcaption>First pass</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*HA0wbW1IrNA6OY_t" /><figcaption>Second pass</figcaption></figure><p>And since we are here, let’s take a look at the Render Graph Viewer</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*EaO4IJmC7Iti-Zeg" /><figcaption>Mat2Layer passes 1 and 2…</figcaption></figure><h4><strong>Conclusion: Blobs and Beyond</strong></h4><p>And there you have it. We’ve ventured from abstract equations to smooth, organic shapes dancing across your screen. Metaballs may seem like simple blobs at first, but behind them lies a world of blending functions, threshold values, and a bit of computational magic.</p><p>The next time you see those mesmerizing, fluid visuals in games or animations, remember that it’s more than just pixels — it’s a mix of math and code working together.</p><p>I hope you enjoyed reading this tutorial and, as usual, comments, doubts, questions, suggestions, etc… are all welcome!</p><p>Until next time!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=22fa1dc07fde" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[LiP] The Magic of 3D: Projecting a Cube in Rust]]></title>
            <link>https://medium.com/@mayo-nesso/lip-the-magic-of-3d-projecting-a-cube-in-rust-527214dae706?source=rss-9d59c510be07------2</link>
            <guid isPermaLink="false">https://medium.com/p/527214dae706</guid>
            <category><![CDATA[matrix]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[ascii]]></category>
            <category><![CDATA[learn-in-public]]></category>
            <dc:creator><![CDATA[Mayo Nesso]]></dc:creator>
            <pubDate>Wed, 28 Aug 2024 13:31:29 GMT</pubDate>
            <atom:updated>2024-10-13T11:36:50.162Z</atom:updated>
            <content:encoded><![CDATA[<h3>[LiP] Projecting a 3D ASCII Cube in Rust</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HpWvlrZrEz7ZEaCDjEr0dA.jpeg" /><figcaption>Photo by <a href="http://www.instagram.com/thebigmayowski">thebigmayowski</a></figcaption></figure><h4>TLDR</h4><p>[<a href="https://github.com/mayo-nesso/spinning_cube">Source code</a>] — [<a href="https://mayonesso.com/blog/2024/08/cube-projection-in-rust/">Unwalled article</a>]</p><h3>Introduction</h3><p>Hey there! Today, we’re going to dive into something that might make your head spin faster than a dusty CPU fan on a hot summer day: 3D projection… in Rust.</p><p>Yep, I know that sounds crazy, but bear with me.<br>This rudimentary version is just a naive approximation of what game engines do nowadays.</p><p>I was heavily inspired by this video [<a href="https://www.youtube.com/watch?v=p09i_hoFdd0">ASMR Programming — Spinning Cube — No Talking</a>], so thanks to its author!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*wV77BxvT2Z2X8HwLPPoQxw.gif" /><figcaption>It’s alive!</figcaption></figure><h3>The General Idea</h3><p>First, let’s set the plan.<br>The idea is to render a 3D cube on a 2D screen.<br>And to do that, we are going to make use of that thing called Math, and, to make everything a little bit more complicated, we are going to write a program in Rust to achieve our mission.</p><p>So, it’s not your one-line “Hello, World!” program, but it’s not rocket science either. Well, actually, it kind of is — but the fun kind, where no one gets blown up if we mess up a calculation.</p><p>What are we going to do is:</p><p><strong>i.</strong> Take each face of the cube<br><strong>ii.</strong> Apply math to calculate the position in the 3D space, taking into consideration rotations on the X, Y, or Z axis<br><strong>iii.</strong> Draw the points that are closer to the camera, applying some projection to make things bigger or smaller accordingly</p><h3>The Math Behind the Madness</h3><p>Now, before you run away screaming “Math! My old nemesis!”, let me tell you something, we are not trying to publish a post-doc thesis.</p><p>All the formulas that we are going to use look scarier than they really are, so even if math is not your thing, let’s give them a chance!</p><p>In this W<a href="https://en.wikipedia.org/wiki/Rotation_matrix">ikipedia article on the Rotation matrix</a>, they explain to us that:</p><blockquote>…a rotation matrix is a transformation matrix that is used to perform a rotation in Euclidean space.</blockquote><p>This could be performed in two dimensions, but that is not enough for us, so; fast your seat belt; and prepare to travel to the next level; The 3D world!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*pcs6YxeTIgiJULcwrgL2bQ.jpeg" /></figure><p>So <a href="https://en.wikipedia.org/wiki/Rotation_matrix#General_3D_rotations">if we jump to this part of the article</a> we have:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ext5TsPDWY_tCivdroCw4w.png" /></figure><p>This is one of many ways to use math to apply the required transformations (yep, another and arguably better are <a href="https://en.wikipedia.org/wiki/Quaternion">Quaternions</a>!).</p><p>If we multiply this little monster by a [i, j k] vector, we have;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w0x5nPVD3JKWCVdHioG3kQ.png" /></figure><p>which gives us;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*deY7s7rSjoS1MQcAbwcSLw.png" /></figure><p>And that’s all, that is the farther that we are going to go, in terms of esoteric math.<br><strong>That is our rotation matrix!</strong></p><p>This, in a code-friendly style, translates each row to the calculate_x, calculate_y, and calculate_z functions in our rendering.rs file.</p><pre>fn calculate_x(<br> i: f32, <br> j: f32, <br> k: f32, <br> alpha: f32,<br> beta: f32,<br> gamma: f32,<br>) -&gt; f32 {<br> let sin_a = alpha.to_radians().sin();<br> let cos_a = alpha.to_radians().cos();<br> let sin_b = beta.to_radians().sin();<br> let cos_b = beta.to_radians().cos();<br> let sin_c = gamma.to_radians().sin();<br> let cos_c = gamma.to_radians().cos();<br>i * cos_a * cos_b // &lt; - - I re arranged the terms a little bit, first `i` components, then `j`, then `k`…<br> + j * cos_a * sin_b * sin_c<br> - j * sin_a * cos_c<br> + k * cos_a * sin_b * cos_c <br> + k * sin_a * sin_c<br>}<br>…</pre><h4>Why So Trigonometric?</h4><p>You might be wondering, “Why all the sines and cosines? Did the developer have a particular fondness for waves?” Well, not exactly. Sines and cosines are like The Good, the Bad and the Ugly of rotation in mathematics (in other words, a classic). They allow us to describe circular motion, which is exactly what we need for rotation.</p><p>Let’s break it down:</p><p><strong>i.</strong> alpha, beta, and gamma are our rotation angles around the z, y, and x axes respectively (why this inverse order? Well, remember that Wikipedia article? Different transformations can be used, and this one uses alpha as yaw, beta as pitch, and gamma as roll <a href="https://simple.wikipedia.org/wiki/Pitch,_yaw,_and_roll">angles</a>)<br><strong>ii.</strong> We convert these angles to radians (because computers prefer radians to degrees, they’re quirky like that)<br><strong>iii.</strong> We calculate the sine and cosine of each angle<br><strong>iv.</strong> We then combine these values in a specific way to rotate our point</p><h3>From 3D to 2D: The Projection</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*xyzqFfNT8MYSOIosOBLqbQ.jpeg" /></figure><p>Now that we’ve rotated our point, we need to project it onto our 2D screen. This is where the calculate_for_surface function comes in:</p><pre>fn calculate_for_surface(<br>  cube_x: f32,<br>  cube_y: f32,<br>  cube_z: f32,<br>  ch: char,<br>  z_buffer: &amp;mut [f32; CANVAS_WIDTH * CANVAS_HEIGHT],<br>  buffer: &amp;mut [char; CANVAS_WIDTH * CANVAS_HEIGHT],<br>  alpha: f32,<br>  beta: f32,<br>  gamma: f32,<br>  distance_from_camera: f32,<br>  projection_scale: f32,<br>) {<br>  // Apply rotation transformations to the cube coordinates<br>  let x = calculate_x(cube_x, cube_y, cube_z, alpha, beta, gamma);<br>  let y = calculate_y(cube_x, cube_y, cube_z, alpha, beta, gamma);<br>  let z = calculate_z(cube_x, cube_y, cube_z, alpha, beta, gamma);<br>  // Adjust z-coordinate based on camera distance<br>  let z = z - distance_from_camera;<br>  <br>  // Calculate the inverse of z (ooz: one over z)<br>  //<br>  // In our coordinate system:<br>  // i.- z = 0 is at the origin<br>  // ii.- Positive z values are closer to the camera<br>  // iii.- Negative z values are further from the camera<br>  //<br>  // After subtracting distance_from_camera, all z values become negative<br>  // Smaller negative z values are closer to the camera<br>  let ooz = - 1.0 / z;  <br>  // So now, regarding ooz:<br>  // i.- All values are positive<br>  // ii.- Larger values indicate closer proximity to the camera (useful for z-buffer comparisons)<br>  <br>  // Convert 3D coordinates to 2D screen space<br>  // Note how we use here ooz to &#39;shrink&#39; or &#39;expand&#39; the projection<br>  // xp = ... + x * ...: Positive x values move the point to the right, so we add to the center of the screen.<br>  let xp = (CANVAS_WIDTH as f32 /2.0 + x * ooz * projection_scale * ASPECT_RATIO) as isize;<br>  // yp = ... - y * ...: Positive y values (in 3D space) move the point up, <br>  // but because screen space has the y-axis increasing downward, we subtract to move the point upward on the screen.<br>  let yp = (CANVAS_HEIGHT as f32 / 2.0 - y * ooz * projection_scale) as isize;<br><br><br>  // Check if (xp,yp) point is within the canvas boundaries<br>  if xp &lt; 0 || (xp as usize) &gt;= CANVAS_WIDTH {<br>      return;<br>  }<br>  if yp &lt; 0 || (yp as usize) &gt;= CANVAS_HEIGHT {<br>      return;<br>  }<br>  <br>  // Calculate the buffer index for the current point<br>  let idx = xp + yp * CANVAS_WIDTH as isize;<br>  let idx = idx as usize;<br>  <br>  // Update the z-buffer and character buffer only if this point is closer to the camera<br>  if ooz &gt; z_buffer[idx] {<br>      z_buffer[idx] = ooz;<br>      buffer[idx] = ch;<br>  }<br>}</pre><p>This function is where the magic happens. It takes our 3D point, rotates it, and then projects it onto our 2D canvas.</p><h4>The Secret Sauce: Perspective Division</h4><p>The key to this projection is the line let ooz = — 1.0 / z;. This is called perspective division, and it’s what gives our projection that 3D feel.</p><p>Here’s why it works:</p><p><strong>i.</strong> Objects further away appear smaller.<br><strong>ii.</strong> In our 3D space representation, smaller z values mean the point is further away (and since our origin is 0.0, the more negative a value is, is further away). And since we push away our points even more with - distance_from_camera all our points should be expected to be negative.<br><strong>iii.</strong> By multiplying by -1.0 and dividing by z, we have a variable ooz that has positive values. The larger the closer to the camera, the bigger that should be on screen.<br><strong>iv.</strong> So, points with smaller ooz values (further away) have a smaller effect on the final x and y coordinates.</p><p>It’s like squishing the entire 3D world onto your flat screen, with the added bonus of making far-away things look… well, far away.</p><h3>Putting It All Together</h3><p>Now that we understand the individual pieces, let’s look at how they all fit together in the draw_cube function:</p><p>Here we will ‘draw’ each face of the cube, point by point.</p><pre>ub fn draw_cube(<br>  z_buffer: &amp;mut [f32; CANVAS_WIDTH * CANVAS_HEIGHT],<br>  buffer: &amp;mut [char; CANVAS_WIDTH * CANVAS_HEIGHT],<br>  params: &amp;CubeParameters,<br>) {<br>  let mut cube_x = -1.0 * HALF_CUBE_WIDTH as f32;<br>  while cube_x &lt; HALF_CUBE_WIDTH as f32 {<br>    let mut cube_y = -1.0 * HALF_CUBE_WIDTH as f32;<br>    while cube_y &lt; HALF_CUBE_WIDTH as f32 {<br>      // Axis following Rigth-Hand rule<br>      //      y<br>      //      |<br>      //      |<br>      //      |_ _ _ _ x<br>      //     /<br>      //    /<br>      //   z<br>      //<br><br>      //  Plane K; Back side, We update X and Y, and keep Z constant <br>      //        ______<br>      //      /|  K   |<br>      //     / |      |<br>      //    |  |______|<br>      //    | /      /<br>      //    |/______/<br>      //<br>      let x_value = cube_x;<br>      let y_value = cube_y;<br>      let z_value = -1.0 * (HALF_CUBE_WIDTH as f32);<br>      calculate_for_surface(<br>        x_value, y_value, z_value,<br>        &#39;K&#39;,<br>        z_buffer, buffer,<br>        params.alpha, params.beta, params.gamma,<br>        params.distance_from_camera, params.projection_scale);<br><br>      // Plane F; Front side, We update X and Y, and keep Z constant <br>      //       ______<br>      //     /      /|<br>      //    /______/ |<br>      //    |      | |<br>      //    |  F   | /<br>      //    |______|/<br>      //<br>      let x_value = cube_x;<br>      let y_value = cube_y;<br>      let z_value = 1.0 * (HALF_CUBE_WIDTH as f32);<br>      calculate_for_surface(<br>        x_value, y_value, z_value,<br>        &#39;F&#39;,<br>        z_buffer, buffer,<br>        params.alpha, params.beta, params.gamma,<br>        params.distance_from_camera, params.projection_scale);<br><br><br>      // ... draw other faces ...<br><br>      cube_y += params.resolution_step;<br>    }<br>    cube_x += params.resolution_step;<br>  }<br>}</pre><p>This function is like a meticulous artist, painting our cube one point at a time. It loops over the x and y coordinates of each face, calling calculate_for_surface for each point. <br>The result? A beautiful 3D cube rendered on our 2D screen.</p><p>Check how we are using letters to represent each face of the cube:</p><blockquote>F = Front, K = Back,<br>L = Left, R = Right,<br>T = Top, B = Bottom.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*2yQCLQ-xWZWedy2iD-bpLg.png" /></figure><h4>The Z-Buffer: Our Unsung Hero</h4><p>You might have noticed we’re passing around something called a z_buffer. This isn’t just some fancy term we made up to sound smart (although it does sound pretty cool). The z-buffer is crucial for determining which parts of our cube are visible.</p><p>In the calculate_for_surface function, we have this bit of code:</p><pre>// Update the z-buffer and character buffer only if this point is closer to the camera<br>if ooz &gt; z_buffer[idx] {<br> z_buffer[idx] = ooz;<br> buffer[idx] = ch;<br>}</pre><p>This is checking if the current point is closer to the camera than what we’ve drawn so far. If it is, we update the z-buffer and draw the point.</p><p>This ensures that closer parts of the cube properly obscure parts that are further away. It’s like giving our rendering engine depth perception!</p><p>And yes, if you were wondering, the concept of <a href="https://en.wikipedia.org/wiki/Z-fighting">Z-fighting</a> had to come from somewhere!</p><h3>Conclusion: From Math to Magic</h3><p>So there you have it, folks. We’ve taken a journey from dry mathematical formulas to a living, breathing 3D cube rendered in ASCII characters. We’ve rotated points in 3D space, projected them onto a 2D plane, and even given our rendering engine the ability to understand depth.</p><p>The next time you’re playing your favorite 3D game, spare a thought for the math happening behind the scenes. It’s not just pushing pixels — it’s a beautiful dance of trigonometry, linear algebra, and clever programming.</p><p>Remember, every masterpiece of 3D graphics you see, from the latest AAA game to that cool 3D chart in your PowerPoint presentation, is built on these fundamental principles. It’s just that most of the time, the math is hidden away in optimized graphics libraries and powerful GPUs.</p><p>But now you know the secret. You’ve peeked behind the curtain and seen the wizard at work.</p><p>Until next time, keep your code clean, your algorithms efficient, and your cubes rotating!</p><h3>—</h3><p>Finally, the code is in <a href="https://github.com/mayo-nesso/spinning_cube">this repo</a>, all ideas, suggestions, and comments are welcome!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=527214dae706" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[How2]: Rust Code in Unity: A Succinct Guide]]></title>
            <link>https://medium.com/@mayo-nesso/how2-rust-code-as-a-plugin-in-unity-a-succinct-guide-8ae2a57d042f?source=rss-9d59c510be07------2</link>
            <guid isPermaLink="false">https://medium.com/p/8ae2a57d042f</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[plugins]]></category>
            <category><![CDATA[unity]]></category>
            <category><![CDATA[unity-development]]></category>
            <dc:creator><![CDATA[Mayo Nesso]]></dc:creator>
            <pubDate>Wed, 03 Apr 2024 15:11:48 GMT</pubDate>
            <atom:updated>2024-10-13T11:38:21.291Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R30cLxRxns73uF_SoPfXsQ.jpeg" /><figcaption>Photo by <a href="http://www.instagram.com/thebigmayowski">thebigmayowski</a></figcaption></figure><p>TLDR<br>[<a href="https://mayonesso.com/blog/2024/rust-into-unity/">Unwalled article</a>]</p><p>This article discusses the integration of Rust code into Unity, divided into two main sections: Setting up the Rust plugin and integrating it into a Unity project.</p><h3>First: Rust Plugin Setup</h3><p><strong>Create Rust Library Project:</strong> Use Cargo, Rust’s package manager, to create a new Rust library project. Here we will create a library called uni_plugin. <br>Open your terminal and execute the following command:</p><pre>cargo new uni_plugin --lib</pre><p><strong>Specify Dynamic System Library Type:</strong> Update the cargo.toml file to specify the crate type as cdylib for dynamic system library linkage. <br>Our Cargo.toml will look like this:</p><pre>[package]<br>name = &quot;uni_plugi&quot;<br>version = &quot;0.1.0&quot;<br>edition = &quot;2021&quot;<br><br>[lib]<br>crate-type = [&quot;cdylib&quot;]<br><br>[dependencies]</pre><p><strong>Write Rust Code:</strong> Define the desired functionality in lib.rs. Since the purpose of this guide is to test the library integration, we will use the code provided by default.</p><p>Peeeero, pero, pero, to properly “export” the functions with the name that we defined (and avoid Rust doing some magic that usually does to avoid naming clashes when linking) we have to use the attribute#[no_mangle]. Failing to do this will result in an EntryPointNotFoundException since the method will not be found.</p><p>So our <em>almost by default</em> code will be something like this:</p><pre>#[no_mangle]<br>pub fn add(left: usize, right: usize) -&gt; usize {<br>    left + right<br>}<br><br>#[cfg(test)]<br>mod tests {<br>    use super::*;<br><br>    #[test]<br>    fn it_works() {<br>        let result = add(2, 2);<br>        assert_eq!(result, 4);<br>    }<br>}</pre><p><strong>Build the Library</strong>: Build the Rust library by executing this on the terminal:</p><pre>cargo build</pre><p>This will generate the library file in the target/debug directory. On Windows, the file will have a .dll extension, on macOS .dylib, and on Linux .so.</p><h3>Second: Unity Integration</h3><p><strong>Create Plugins Folder:</strong> On your Unity project, create a new folder named Plugins inside the Assets directory, then;</p><p><strong>Move/Copy Library File:</strong> Move or copy the generated library file (libuni_plugin.so/dylib/dll) into the Plugins folder.</p><figure><img alt="Plugins folder" src="https://cdn-images-1.medium.com/max/852/1*VgFJoEnHuNqeQfe5zc7HVw.png" /><figcaption>Plugins folder with our library</figcaption></figure><figure><img alt="Inspector view of our library" src="https://cdn-images-1.medium.com/max/908/1*C-KoRFGFPncpYnrkekaFvQ.png" /><figcaption>Inspector view of our library</figcaption></figure><p><strong>Unity Invocation:</strong> To utilize the functionality provided by the Rust library in Unity, create a C# script. For example, create a script named LibraryInvocation.cs in the Assets/Scripts folder. Attach this script to a GameObject in the scene. For instance, create an object named Scripts and add the script as a new component.</p><p><strong>Here’s the important part; How do we execute the code of our Rust library in our C# code?</strong></p><p><strong>Declare External Function:</strong> In the C# script, declare the external function using the DllImport attribute. Adjust the library name and method signature accordingly. Our code looks like this:</p><pre>using System.Runtime.InteropServices;<br>using UnityEngine;<br><br>public class LibraryInvocation : MonoBehaviour<br>{<br>#if UNITY_IPHONE<br>    [DllImport (&quot;__Internal&quot;)]<br>#else<br>    [DllImport (&quot;Libuni_plugin&quot;)]   <br>#endif<br>    private static extern uint add(uint left, uint right);<br>    <br>    private void Start()<br>    {<br>        Debug.Log(&quot;Starting the app&quot;);<br>        <br>        var results = add(1, 2);<br>        Debug.Log($&quot;Results from library: {results}&quot;);<br>    }<br>}</pre><p>Now, press play to see the results displayed in the console.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aYkcGsFirCoZbNzTloLHqQ.png" /></figure><h4>Notes and Links:</h4><p>Exporting the library to different platforms would depend on the platform itself, <a href="https://rjgameiro.medium.com/let-fun-rust-unity-f7f62609ba49">here is an excellent resource</a> to go deeper on this.</p><p>Source of inspiration: <a href="https://samrambles.com/guides/window-hacking-with-rust/creating-a-dll-with-rust/index.html#hello_runnerexe">Creating A DLL With Rust</a></p><p>Unity Documentation about plugins: <a href="https://docs.unity3d.com/Manual/NativePlugins.html">Native Plugins</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8ae2a57d042f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[LiP] Simple real-time Human Pose Estimation in Python with OpenCV, MoveNet, and UDP Sockets]]></title>
            <link>https://medium.com/@mayo-nesso/lip-simple-real-time-human-pose-estimation-in-python-with-opencv-movenet-and-udp-sockets-54446c1368cb?source=rss-9d59c510be07------2</link>
            <guid isPermaLink="false">https://medium.com/p/54446c1368cb</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[udp-protocol]]></category>
            <category><![CDATA[human-pose-estimation]]></category>
            <category><![CDATA[movenet]]></category>
            <category><![CDATA[tensorflow]]></category>
            <dc:creator><![CDATA[Mayo Nesso]]></dc:creator>
            <pubDate>Thu, 22 Feb 2024 12:16:17 GMT</pubDate>
            <atom:updated>2024-10-13T11:40:32.984Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D43gxsX_roag9bhczN7Z9A.jpeg" /><figcaption>Photo by <a href="http://www.instagram.com/thebigmayowski">thebigmayowski</a></figcaption></figure><p>TLDR<br>[<a href="https://github.com/mayo-nesso/pose-transmitter">Source code</a>] — [<a href="https://mayonesso.com/blog/2024/pose-estimation/">Unwalled article</a>]</p><p>In another episode of <a href="https://www.swyx.io/learn-in-public">Learn in Public</a>, I am here to share a little experiment that uses MoveNet to estimate a human pose of a video or the input from a webcam. Then we’ll send that information through our old and loved <em>User Datagram Protocol</em> (yes, these are the words behind <a href="https://en.wikipedia.org/wiki/User_Datagram_Protocol">UDP</a>).</p><h3>Basic Structure</h3><p>We are going to use Python and the program can be logically split into three parts:</p><p>i.- <strong>Inference Hub</strong>: <a href="https://blog.tensorflow.org/2021/05/next-generation-pose-detection-with-movenet-and-tensorflowjs.html">MoveNet</a> is a model offered by TensorFlow. Following the instructions/idea from this <a href="https://www.tensorflow.org/hub/tutorials/movenet">tutorial</a>, in the <em>Inference Hub, </em>we initialize the desired flavor of the model and set up the details to invoke the inference method<em>.</em></p><p>ii.- <strong>Frame Analysis</strong>: Since we are analyzing a video we will extract frame by frame using <a href="https://opencv.org/">OpenCV</a> to calculate the inference in a loop.</p><p>iii.- <strong>Pose Keypoints Transmission</strong>: The last part is all about transmitting the serialized results of the inference over the network using UDP.</p><h3>Inference Hub</h3><p>This part of the is pretty straightforward, following the <a href="https://www.tensorflow.org/hub/tutorials/movenet">tutorial</a> from the TensorFlow site, we extract the functionality to <a href="https://www.tensorflow.org/hub/tutorials/movenet#load_model_from_tf_hub">download the desired model</a> (we can choose from <em>lightning</em> or <em>thunder</em>, and its FP16 and INT8 quantized versions) with its inference invocation, the logic to <a href="https://www.tensorflow.org/hub/tutorials/movenet#cropping_algorithm">crop a relevant section</a> of the frame (this will help us to make our inference more efficient), and the logic to convert the inference results to key points and edges.</p><p>The code is inside pose_transmitter/inference_hub with hub.py as the main entry point.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ivq6Gzrw3xwhDkruhK_bug.png" /></figure><h3>Frame Analysis</h3><p>The frame extraction is made in the <em>VideoPoseProcessor</em> object where after the initialization we are ready to run a loop processing our video.</p><p>The code is in pose_transmitter/video_pose_processor.py</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MiTZDjMPQIiHWKP2k2loLA.png" /></figure><p>The parameters are the <em>inference_hub</em>, the <em>source,</em> and a <em>debug</em> flag.</p><p>We use <a href="https://docs.opencv.org/4.x/d8/dfe/classcv_1_1VideoCapture.html">OpenCV</a> to work with the video file or with the camera input.<br>The processing loop is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tpp-H96ATl1GNnkNY1tc9A.png" /></figure><p>Note the line 23 of the snapshot. Since OpenCV represents the colors of the image in the <a href="https://learnopencv.com/why-does-opencv-use-bgr-color-format/">Blue-Green-Red (BGR</a>) format we need to convert it to the Red-Green-Blue (RGB) format, which is the format that TensorFlow uses.</p><p>After running the inference we invoke the callback (in case of being defined) and <em>display_results</em> if specified.</p><p>The entry point of this logic is the method called start_processing with the only parameter the callback to process the results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8Mv50gWR954MSSLzHkZnyA.png" /></figure><p>Note how if the debug flag is present, we cannot use threads. <br>The logic behind this is that all the operations that display UI elements have to run in the main thread. And since the debug flag implies showing the results on the screen (display_results=True) we are limited in this regard.</p><h3><strong>Pose Keypoints Transmission</strong></h3><p>The data to be transmitted is serialized and then placed in a Queue. A worker thread is responsible for retrieving and dispatching these messages.</p><p>Our selection in this case is a simple UDP transmission, serializing the data using <a href="https://msgpack.org/index.html">MsgPack</a> which is a simpler approach compared to other serialization methods (like <a href="https://protobuf.dev/">Procolol Buffers</a> or <a href="https://thrift.apache.org/">Thrift</a>), with support in different languages, and also more efficient than simple JSON.</p><p>The code is in pose_transmitter/qudp_transmitter.py<br>We can see the constructor for QUDPTransmitter here:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BYR7gJxM5TlqIWLSmpx4EQ.png" /></figure><p>The code that receives the data (and what is used as the callback in the VideoPoseProcessor is this put_message method:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YPZIwDHqaCkGUcQ2HwHYdA.png" /></figure><p>We activate the worker thread with _activate_transmission and while is active, we will be sending the data through _send:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JQrMbot8Qq_Dwsmp43pRgQ.png" /></figure><p>The data consists of a list of pairs of floating-point values (List&lt;float[]&gt;):</p><pre>[<br>    [989.79443359375, 635.7391967773438], <br>    [1091.6107177734375, 512.1315307617188], <br>    [889.1296997070312, 520.48828125], <br>    [1204.3812255859375, 533.4179077148438], <br>    ...<br>]</pre><blockquote>📝 Note:<br>It is worth noting that not all points may be inferred and included in the list.<br>For example, if the camera captures half of a person’s body, it will send the information of the points found on that particular half.</blockquote><h3>Finally, all together...</h3><p>The last part (ok, this time <em>is</em> the actual ‘last part’) is putting it all together.</p><p>The main.py file in our project is in charge of that.</p><p>We use <a href="https://docs.python.org/3/library/argparse.html">argparse</a> to being able to specify parameters from the terminal:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zPsxG-_PNXfnFBby7z34aQ.png" /></figure><p>And from there, we wrap up the rest:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9PJSQ3FTFmLsn02DtMtfVg.png" /></figure><p>So now we are ready to use the program. <br>On the Terminal, we can execute:</p><pre>python ./pose_transmitter/main.py --video_source 0 --host_ip 127.0.0.1 --host_port 4900</pre><p>or</p><pre>python ./pose_transmitter/main.py --video_source some_video.mp4</pre><p>or just:</p><pre>python ./pose_transmitter/main.py --debug</pre><p>The results are something like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uskLxZ5Xv94_3tm2Z1cdzA.png" /></figure><h3>Notes</h3><p><strong>Performance:</strong> It is <strong>not</strong> really good. I am getting around 30FPS using my webcam (less than that as you can see in the example) which for this experiment <a href="https://www.neogaf.com/threads/the-human-eye-cant-see-more-than-30fps-when-there-are-100-frames-or-more.1471501/">is enough</a>. <br>In the <a href="https://storage.googleapis.com/tfjs-models/demos/pose-detection/index.html?model=movenet">TensorFlow JS Demo</a>, I am getting 3x times that. Same machine, on the browser, Javascript… You get the idea...</p><p>Even in Python, I am pretty sure that there is space for improvement. Maybe the fact that I am extracting the frame, then doing the inference, and then extracting the next frame. <br>Couldn’t be parallel tasks? Not sure how the Global Interpreter Lock will limit this, but is an interesting exercise that is pending.</p><h4>—</h4><p>Finally, the code is in <a href="https://github.com/mayo-nesso/pose-transmitter">this repo</a>, all ideas, suggestions, and comments are welcome!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=54446c1368cb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[LiP] Upwork Adventures; Fixing a Unity/XCode Build Error]]></title>
            <link>https://medium.com/@mayo-nesso/lip-unity-agora-xcode-build-fix-cd2f9661f89c?source=rss-9d59c510be07------2</link>
            <guid isPermaLink="false">https://medium.com/p/cd2f9661f89c</guid>
            <category><![CDATA[learn-in-public]]></category>
            <category><![CDATA[unity]]></category>
            <category><![CDATA[unity3d]]></category>
            <category><![CDATA[upwork]]></category>
            <dc:creator><![CDATA[Mayo Nesso]]></dc:creator>
            <pubDate>Tue, 13 Feb 2024 17:24:20 GMT</pubDate>
            <atom:updated>2024-08-25T10:14:23.702Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="‘El hombre teme a lo que desconoce’" src="https://cdn-images-1.medium.com/max/1024/1*fmh1kKTDSaiqLL86d6crBg.jpeg" /><figcaption>Photo by <a href="http://www.instagram.com/thebigmayowski">thebigmayowski</a></figcaption></figure><p><a href="https://mayonesso.com/blog/2024/agora-unity-xcode-build-errors/"><em>&gt;&gt;&gt;[post on mayonesso.com]</em>&lt;&lt;&lt;</a></p><p>So, inspired by <a href="https://www.swyx.io/learn-in-public">learn in public</a> (yep, from that comes the [LiP] tag…) I am here sharing a little adventure that I came up with last week exploring UpWork trying to find something that my <em>zero-rating-zero-stars</em> status could be enough for.</p><p>So, looking into Unity stuff I saw a problem in the signing process that someone has faced when building an OSX build.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*iX4nAIB46DBSrCtQ93Jd2w.jpeg" /><figcaption>Yep, XCode build errors…</figcaption></figure><p>But the good thing (and a smart move) from the job description was that all the repro steps were there. “Is this Unity version”, “Is this specific plugin”, “These are the repro steps”, “And this is the error that we are getting”.</p><h3>Digging into it…</h3><p>While my gf was fighting with her landlord over emails before we went dining out. I downloaded the plugin, set the specific Unity version, and got the same error with the delightful repro steps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/514/1*FtfdStsa5uBq_PgRP-Occw.png" /><figcaption>The easy part.</figcaption></figure><p>Then I started to look at what could be the error, by the stack trace I saw that some files from the specific plugin that was described (<a href="https://github.com/AgoraIO-Extensions/Agora-Unity-Quickstart">Agora</a>) weren’t signed properly, and the build would be completed if these files from the plugin were removed. So, yes, it was the plugin.</p><p>Further investigation, I saw some people having the same issue on the <a href="https://github.com/AgoraIO-Extensions/Agora-Unity-Quickstart/issues/224">plugin repo</a>, and some people having the same issue on other plugins or files in Stackoverflow it was clear that had something to do with the signature and a re-signature had to be made.</p><p>But then, exploring the plugin files I saw two scripts called prep_codesign.sh and signcode.sh ! In a careless move ( 🤞 no rm -rfpls!), I ran the scripts fixing the parameters / ENV variables that were asking me, but no luck. <br>The error on Xcode was still there and my girlfriend was almost done with the current round. I jumped back to the console and started to read the scripts a little bit more carefully, and then I recognized part of a path that they were using. But the pattern was slightly different. What if… swapped the line to match the structure that I had, ran the scripts, and voila! A green light on XCode!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/417/1*3zfuzZtW5jw0InkYdUkGHA.jpeg" /></figure><h3>What next?</h3><p>This wasn’t a theoretical exercise, but a <strong>weird</strong> attempt to try to get my first job on UpWork, so I went to the platform, took some screenshots of before/after (I had to create a new project &amp; fix it again), and wrote a “presentation” letter <strong>completely different</strong> from my other attempts where I try to, somehow with words, convince someone that I actually can do the job at hand. <br>This time I just said that I was able to reproduce the error, that I found a fix, and that I was attaching screenshots of it.</p><p><strong>“Show, don’t tell”</strong> in its maximum expression.</p><p>The next day I receive a message from the client and we start working on the solution. A <em>few</em> simple steps that in the end I put together in a PostBuild script + a ScriptableObject to set up the required parameters (<a href="https://github.com/mayo-nesso/AgoraPostBuildFix">repo here</a>).</p><h3>Some lessons learned…</h3><p>Maybe the most important one is that <strong>if you don’t have a reputation in places like UpWork, you will have to work for reduced fees or even for free</strong>.<br>But the point is to do it smartly. Pick a j̵o̵b̵ task that you can finish in a reasonable amount of time and just do it. Before even applying. <br>Two things could happen a.- once you are done you offer the solution (with proof!), or b.- the job was taken and at least you learned something (hopefully).</p><p>The second one, try to achieve a balance in the karmic receive/give thing… Make a post like this, put the solution to Github, share your experience… <br>I don’t have good arguments on this one, but I think it does more good than harm.</p><h3>…</h3><p>If everything goes well I will share more experiences of fixing or doing stuff here, hopefully not only a recipe to fix something but some not-that-well-curated reflexions too!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cd2f9661f89c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[How2]: Connecting to Firestore (a NoSQL db) with Python!]]></title>
            <link>https://medium.com/@mayo-nesso/connecting-to-firestore-a-nosql-db-with-python-06219f052fd6?source=rss-9d59c510be07------2</link>
            <guid isPermaLink="false">https://medium.com/p/06219f052fd6</guid>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[google-credentials]]></category>
            <category><![CDATA[service-account-key]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[firestore]]></category>
            <dc:creator><![CDATA[Mayo Nesso]]></dc:creator>
            <pubDate>Sat, 06 Jan 2024 18:41:27 GMT</pubDate>
            <atom:updated>2024-09-08T17:28:53.758Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cgeikbDNFibjEp40MOmVJg.jpeg" /><figcaption>Photo by <a href="http://www.instagram.com/thebigmayowski">thebigmayowski</a></figcaption></figure><p><a href="https://mayonesso.com/blog/2024/08/connecting-to-firestore-with-python/"><em>&gt;&gt;&gt;[post on mayonesso.com]</em>&lt;&lt;&lt;</a></p><p>Firestore is a NoSQL database that is part of the services that Google offers.</p><p>In this article, we will create a connection to this database, taking advantage of the fact that (like other Google products) it has a <a href="https://cloud.google.com/firestore/pricing">free tier</a>.</p><p>The first step is to create a FireStore database in a Google project.</p><p>To do this, we visit the <a href="https://console.cloud.google.com/">Google Cloud Console</a>, and on the Databases category select Firestore.</p><figure><img alt="Firestore selection on Google Console" src="https://cdn-images-1.medium.com/max/294/1*4NusQt3XW2uz1NMbMPzfVQ.png" /></figure><p>Here we will create a new database.<br>We select the native mode:</p><figure><img alt="Firebase creation. Selecting the Native mode." src="https://cdn-images-1.medium.com/max/1024/1*XeSdBF7BE1nrbSw7f1fcCg.png" /></figure><p>Assign it a name*, and a zone:</p><figure><img alt="Firebase creation. Selecting the Name and Region." src="https://cdn-images-1.medium.com/max/1024/1*8vMddC4Ha7aNK37KHK8dJA.png" /></figure><p>*If we want to be in the <a href="https://cloud.google.com/firestore/pricing">free tier</a>, we must leave the name of our database to the default value, that is, (default).</p><p>Then we select the “Production Rules” mode.</p><figure><img alt="Firebase creation. Selecting the Security Rules." src="https://cdn-images-1.medium.com/max/824/1*1Imx2fJ52DYXIH6z6Fjxlg.png" /></figure><p>And that’s it, we now have our db, and we can start creating our collections and documents:</p><figure><img alt="Firestore default database" src="https://cdn-images-1.medium.com/max/1024/1*bZMO2hzS2MsBeoEtulLtyQ.png" /></figure><p>Buuuut, what we really want to do is be able to do these types of operations from a Python script located in an external server.</p><p>And for that, we need the following!<br>1.- Install the dependencies in our project<br>2.- Obtain a service account to be able to authenticate correctly<br>3.- Authenticate to Firestore</p><h4>1.- Install the dependencies in our project</h4><p>Here we’ll use poetry to install the package firebase-admin:</p><pre>poetry add firebase-admin</pre><p>And that is!</p><h4>2.- Obtain a service account to be able to authenticate correctly</h4><p>Again, we go to the Google Cloud Console, to the IAM &amp; Admin &gt; <a href="https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts">Service accounts</a> section and we create a new Service Account:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K0vV_Cs5BZGIv0K5uIHh5Q.png" /></figure><p>We fill in the details:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/772/1*2e2eCg8In_TUU60ayPMMgA.png" /></figure><p>Click in CREATE AND CONTINUE , and after the Service Account is created, we assign the Cloud Datastore User role (It seems to be a legacy name, DataStore instead of Firestore).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M7QiyWF-nLryp7W9cnDUwg.png" /></figure><p>Other alternatives that also work are: Firebase Admin SDK Administrator Service Agent or Firebase Develop Admin.<br>Note that roles like: Firestore Service Agent, or Firestore Editor don’t seem to work.</p><p>We left the last section as it is;</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LZENDDHr8tFgkJAggxrLIw.png" /></figure><p>Once we click DONE our Service Account is ready to be used!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DB5fj9Q-onU7W8-PnyFu1w.png" /></figure><p>Now, we have to download the file with the credentials that we will use!</p><p>As we already can see our new service account is listed, so we click it to see the details.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2a7cxYOKECKDFt6n7nv_vA.png" /></figure><p>Then navigate to Keys, and add a new key in JSON format!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6f1kO9Gkslxe332twJS4Ig.png" /></figure><p>Select JSON type and CREATE. <br>This will create the key and download it to our computer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*flKF0MuUCGBIyEfqweVS4g.png" /></figure><h4>3.- Authenticate to Firestore</h4><p>We already have our service account and the corresponding key in a JSON file.<br>The next thing is to use this file to authenticate ourselves and be able to interact with our BDD, here is a sample code.</p><blockquote><strong>Important</strong>; this JSON file is a password (a key to be more specific) that can present a security risk in the event of a leak or compromise.<br>It is very important to be careful where we store it, and keep in mind that, since it is a file, it can be mistakenly uploaded to a repository, or to an application that is on the client’s side!</blockquote><pre>import firebase_admin<br>from firebase_admin import credentials<br>from firebase_admin import firestore<br><br>cred = credentials.Certificate(&#39;./path/to/your_service_account.json&#39;)<br>firebase_admin.initialize_app(cred)<br>db = firestore.client()<br><br><br># Write and Append examples:<br><br># Write data with `set`!<br>data = {&quot;name&quot;: &quot;Los Angeles&quot;, &quot;state&quot;: &quot;CA&quot;, &quot;country&quot;: &quot;USA&quot;}<br>db.collection(&quot;cities&quot;).document(&quot;LA&quot;).set(data)<br><br># Append data with `merge=True`<br>db.collection(&quot;cities&quot;).document(&quot;BJ&quot;).set({&quot;capital&quot;: True}, merge=True)<br><br><br># Read data with get():<br>doc_ref = db.collection(&quot;cities&quot;).document(&quot;BJ&quot;)<br>doc = doc_ref.get()<br>print(doc.to_dict())</pre><p>And listo!</p><p>Useful links:</p><p><a href="https://cloud.google.com/compute/docs/authentication">Google Docs: Authenticate to Compute Engine</a><br><a href="https://cloud.google.com/docs/authentication/provide-credentials-adc#wlif-key">Google Docs: Setting up a Service Account</a><br><a href="https://firebase.google.com/docs/firestore/quickstart">Google Docs: Firestore quickstart</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=06219f052fd6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[How2]: Running Google Jobs with a custom Docker image]]></title>
            <link>https://medium.com/@mayo-nesso/running-google-jobs-with-a-custom-docker-image-dbf1cd0097c1?source=rss-9d59c510be07------2</link>
            <guid isPermaLink="false">https://medium.com/p/dbf1cd0097c1</guid>
            <category><![CDATA[google-run]]></category>
            <category><![CDATA[google-scheduled-jobs]]></category>
            <category><![CDATA[scripting]]></category>
            <category><![CDATA[cron]]></category>
            <dc:creator><![CDATA[Mayo Nesso]]></dc:creator>
            <pubDate>Thu, 04 Jan 2024 21:03:53 GMT</pubDate>
            <atom:updated>2024-08-11T10:38:17.251Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rWmzqcqDC62drkNmQwcx7Q.jpeg" /><figcaption>Photo by <a href="http://www.instagram.com/thebigmayowski">thebigmayowski</a></figcaption></figure><p>If you’ve ever found yourself repeating a task quite frequently, this article may help you!</p><p>Google has a product called Google Run, where you can, in their own words `Run applications fast and securely in a fully managed environment`.</p><p>Now, a feature is the possibility of executing scheduled jobs. These are the so-called `Jobs`. If your task is a small script, you could benefit from the <a href="https://cloud.google.com/run/pricing">free tier.</a></p><p>In this column we will see how to create a `Job` following one of the recommended methods, that is, packaging our code in a Docker image.</p><p><strong>First</strong>, to work with Google Run Jobs, you need to have one of the following roles:</p><p>- Cloud Run Admin: This gives you full permissions to create, update, delete, and run jobs.<br>- Cloud Run Invoker: This role allows you to run jobs.<br>- Cloud Run Viewer: It provides the ability to view and list jobs.</p><p>Why, you ask? Well, because the necessary permissions include exciting things like:</p><p>- run.jobs.{create/update/run/delete/get/list/getIamPolicy/setIamPolicy}<br>- run.executions.{get/list/delete}<br>- run.tasks.{get/list}</p><p>(Also remember this list if you are creating a custom role)</p><p><strong>Next</strong>, you’ll need a container image. You can choose one of these types of container images:</p><p>- Container images that are stored in the same project as the one you’re creating the job or service in.<br>- Container images from other Google Cloud projects (assuming you have the right IAM permissions).<br>- Public container images from Artifact Registry, Container Registry (Deprecated), or the Docker Hub.</p><p>But, hold onto your hat, because Google recommends the use of Artifact Registry. So that is what are we going to do!</p><p><strong>And</strong> then you will be able to select your image and run your code as a scheduled Job.</p><p><strong>&gt;&gt; R</strong><a href="https://github.com/mayo-nesso/google-job-artifact"><strong>epository link here</strong></a><strong>.</strong></p><h4><strong>Creating a container image</strong></h4><p>So, according to Google, we’re going to use Artifact Registry because it’s the cool kid on the block. As Google puts it, “Artifact Registry provides a single location for storing and managing your packages and Docker container images.”</p><p>There are three types of repositories:</p><p>- <strong>Standard</strong>: These are regular repositories for your private artifacts. You upload and download artifacts directly with these repositories and use Artifact Analysis to scan for vulnerabilities and other metadata.<br>- <strong>Remote</strong>: This is like a repository that goes on vacation. It acts as a proxy for an external source, like Docker Hub, Maven Central, or the Python Package Index (PyPI). It fetches stuff from an external source and keeps a copy. Lazy but effective!<br>- <strong>Virtual</strong>: This is the master of ceremonies. It acts as a single access point to download, install, or deploy artifacts from one or more upstream repositories. Virtual repositories simplify client configuration for consumers of your artifacts.</p><p>(Want more details? [<a href="https://cloud.google.com/artifact-registry/docs/repositories">Google: Repository Overview</a>])</p><p>Although there are different ‘artifact’ options we will focus on creating a Docker artifact in a Standard repository.</p><p>For this, we will do the following steps:</p><p>0.- Create a new Google Project<br>1.- Create a private Docker repository in Artifact Registry<br>2.- Set up authentication (this will allow Docker to push the image to our Artifact Registry in Google)<br>3.- Push an image to the repository</p><h4><strong>0.- Create a new Google Project</strong></h4><p>We will create a new project: cumplo-jobs, make sure Billing is activated, and then activate the `Artifact Registry API`.</p><p>Then we will install Docker and Google Cloud CLI.</p><p>We initialize the Google Cloud CLI and when prompted, we select the project we just created.</p><pre>gcloud init</pre><p>An alternative is to simply specify the project-id we want to work with:</p><pre>gcloud config set project cumplo-jobs</pre><h4><strong>1.- Create a private Docker repository in Artifact Registry</strong></h4><p>Run the following command to create a new Docker repository named cumplo-jobs-docker-repo in the location us-central1 with the description &quot;Docker repository for Cumplo Jobs&quot;.</p><pre>gcloud artifacts repositories create cumplo-jobs-docker-repo \<br> --repository-format=docker \<br> --location=us-central1 \<br> --description=&quot;Docker repository for Cumplo Jobs&quot;</pre><p>Which will give us an output similar to this:</p><pre>Create request issued for: [cumplo-jobs-docker-repo]<br>Waiting for operation [projects/cumplo-jobs/locations/us-central1/operations/12abc123-f0ab-12d1-a0ff-0c75bf1ef26a] to complete…done.<br>Created repository [cumplo-jobs-docker-repo].</pre><p>We can check that the repository has been created with:</p><pre>gcloud artifacts repositories list</pre><h4><strong>2.- Set up authentication</strong></h4><p>The first step to being able to push or pull images is to configure Docker so that you can use the Google Cloud CLI to make requests to the Artifact Library.</p><p>For that, we execute this command</p><pre>gcloud auth configure-docker us-central1-docker.pkg.dev</pre><p>Which will give us an output similar to this:</p><pre>Adding credentials for: us-central1-docker.pkg.dev<br>After update, the following will be written to your Docker config file located at [/Users/mayo_nesso/.docker/config.json]:<br> {<br>  &quot;credHelpers&quot;: {<br>    &quot;us-central1-docker.pkg.dev&quot;: &quot;gcloud&quot;<br>  }<br>}<br><br>Do you want to continue (Y/n)?  y<br><br>Docker configuration file updated.</pre><p>This updates our Docker configuration, and we should now be able to push an image!</p><h4><strong>3.- Push an image to the repository</strong></h4><p>Google’s tutorial, guides us to download an image from the Docker repository, but in our case, we will use a locally built image to illustrate a more practical example.</p><p><strong>3.1.- Our docker image</strong></p><p>We have a folder called our_docker_image where we have a Dockerfile and an app.py file, this is enough to illustrate the creation of a Docker image that will be uploaded to the registry.</p><p>We go to our_docker_image folder (where our Dockerfile is located), then we will build an image.</p><p>In this step, we have to carefully specify the name of our container since it has to have some <strong>important details</strong>.</p><p>The <strong>important details</strong> are Repository location, Project id, and the Artifact Repository idthat we have been using.</p><p>In our example, the name of the image will be simple_image, and the tag just tag1.</p><p>So recapitulating we have;</p><p>- Repository location: central1<br>- Project id: cumplo-jobs<br>- Artifact repository id: cumplo-jobs-docker-repo<br>- Image Name: simple_image<br>- tag: tag1</p><p>With all that in mind, the build command will be:</p><pre>cd our_docker_image<br>docker build -t us-central1-docker.pkg.dev/cumplo-jobs/cumplo-jobs-docker-repo/simple_image:tag1 .</pre><blockquote>Important : Since Google Cloud Platform requires an AMD image to run it, if you are on Apple silicon (M1, M2,…), then the generated build will not work<br> (<a href="https://stackoverflow.com/questions/66823012/i-am-using-pycharm-and-cloud-run-plugin-to-deploy-flask-app-to-gcp-on-a-mac-m1-a">more info in this SO thread</a>)!</blockquote><blockquote>To fix that, you will have to follow this steps to create the image!<br>First, check if you have a builder that can create a linux/amd64 image:</blockquote><pre>docker buildx ls</pre><blockquote>Output:</blockquote><pre>NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS<br>default docker<br> default default running v0.11.6+616c3f613b54 linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6<br>desktop-linux * docker<br> desktop-linux desktop-linux running v0.11.6+616c3f613b54 linux/arm64, linux/amd64, linux/amd64/v2, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6</pre><blockquote>If so, we proceed with our _modified_ build command:</blockquote><pre>cd our_docker_image<br>docker buildx build --platform linux/amd64 -t us-central1-docker.pkg.dev/cumplo-jobs/cumplo-jobs-docker-repo/simple_image:tag1 .</pre><blockquote>If we don’t find a builder with the `linux/amd64` capability, we will have to create one (<a href="https://docs.docker.com/build/building/multi-platform/">see here that and other options</a>).</blockquote><h4><strong>3.2.- Pushing our image</strong></h4><p>Finally, to push the image we use this command:</p><pre>docker push us-central1-docker.pkg.dev/cumplo-jobs/cumplo-jobs-docker-repo/simple_image:tag1</pre><p>That will give us something like:</p><pre>The push refers to repository [us-central1-docker.pkg.dev/cumplo-jobs/cumplo-jobs-docker-repo/simple_image]<br>13317470e910: Pushed<br>1fd51346f743: Pushed<br>a130a3c46754: Pushed<br>762ff2f0c641: Pushed<br>a1bf4fc22850: Pushed<br>f94bda6a158c: Pushed<br>3ce829cc4970: Pushed<br>tag1: digest: sha256:c2918dc3efda6123c338669c5a2934f607c40052a8994d3d7b26e23e4b3cf66a size: 1783</pre><p>Then, we can check on the Google Cloud Console, that the repo, and image are there 😃!</p><figure><img alt="Google Artifact Registry" src="https://cdn-images-1.medium.com/max/1024/1*d6KkoEHDU1rkdS402sCtxg.png" /></figure><p>Repository details:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7HWtiC04AzxJi-7lSl5Rng.png" /></figure><p>Image details:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0_1aYEZSEASMpfZ-wYxYNQ.png" /></figure><p>Great, we now have our image in the Artifact Repository!</p><p>Now, to run the Job, we go to our Google Console, select our project, and go to Cloud Run:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MN1xvmE6WvHMegrYTcLuCg.png" /></figure><p>We select `Jobs`:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B0mcouD-e_6xlJgEU32P5w.png" /></figure><p>And there we will create a new Job (`Create Job`):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yCwUEKThxnIyUfi9XQR1kA.png" /></figure><p>We select our image previously uploaded to the `Artifact Registry`:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D6JVVDe9FNla1XtGjD4QUA.png" /></figure><p>We name our job, and create it (We can select “Execute job immediately” to see what results from the execution)!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y-LLC2MT21OLKa5h7cRaLQ.png" /></figure><p>After a moment, our Job will be created!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ETETL0mYC_RodsUiurSNOg.png" /></figure><p>If everything goes well, we should see the green light after our test run:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VlUDEATlwDcu8yprsCvMsw.png" /></figure><p>Job details:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MYUAV6e0fvLF46iZ_3P-og.png" /></figure><p>And voila!</p><p>Useful links:</p><ul><li><a href="https://cloud.google.com/artifact-registry/docs/docker/store-docker-container-images">Google Doc: Store Docker container images in Artifact Registry</a></li><li><a href="https://cloud.google.com/run/docs/create-jobs#console">Google Doc: Create Jobs Docs</a></li><li><a href="https://docs.docker.com/build/building/multi-platform/">Docker Doc: Multi-platform images</a></li><li><a href="https://github.com/mayo-nesso/google-job-artifact">Repo to google-job-artifact</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dbf1cd0097c1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[How2]: Creating Python Virtual Environments with PyEnv: A Succinct Guide]]></title>
            <link>https://medium.com/@mayo-nesso/creating-python-virtual-environments-with-pyenv-a-succinct-guide-360a3e75efdb?source=rss-9d59c510be07------2</link>
            <guid isPermaLink="false">https://medium.com/p/360a3e75efdb</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[virtual-environment]]></category>
            <dc:creator><![CDATA[Mayo Nesso]]></dc:creator>
            <pubDate>Thu, 26 Oct 2023 19:07:37 GMT</pubDate>
            <atom:updated>2024-01-07T17:16:17.831Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="A wall covered with old cassettes. Black and white." src="https://cdn-images-1.medium.com/max/1024/1*-WfEEdireGDoLF4Xd5Vxsw.jpeg" /><figcaption>Photo by <a href="http://www.instagram.com/thebigmayowski">thebigmayowski</a></figcaption></figure><h4>Intro: (you can skip this…)</h4><p>Python Virtual Environments provides an isolated workspace for your projects, allowing you to work with specific Python versions and libraries.</p><p>In this small (minimalist) guide, we&#39;ll walk you through creating a virtual Python environment using pyenv without the need for additional tools like virtualenv or conda.</p><h4>What You’ll Need:</h4><p>Before we dive in, ensure you have the following prerequisites:</p><ul><li>venv: This comes built-in with Python⁰ and allows you to create virtual environments. You can learn more about it in the <a href="https://docs.python.org/3/library/venv.html">Python documentation</a>.</li><li>pyenv: Installation instructions on its <a href="https://github.com/pyenv/pyenv#installation">repository</a>.</li></ul><h3>Step-by-Step Instructions:</h3><p><strong>i.- Install your Desired Python Version</strong><br>First, we will use pyenv to install the Python version that we want to install. For example, if we want to go with 3.8.8:</p><pre>pyenv install 3.8.8</pre><p><strong>ii.- Activate the Specific Python Version</strong><br>Activate the Python version you just installed. This ensures that any subsequent commands will use the desired Python version¹:</p><pre>pyenv shell 3.8.8</pre><p><strong>iii.- Create a New Virtual Environment</strong><br>Now, let’s create a new virtual environment. This environment will be located in a hidden folder² called .virtual_env. <br>Use the following command³:</p><pre>python -m venv .virtual_env</pre><p><strong>iv.- Activate Your New Environment<br></strong>To work within your new virtual environment, activate it with the following command:</p><pre>source .virtual_env/bin/activate</pre><p><strong>v.- (optional) Check the current version<br></strong>If all worked properly, then we’ll see the desired version when we execute:</p><pre>python --version</pre><p><strong>vi.- Deactivate the Virtual Environment<br></strong>When you’re done working in your virtual environment, deactivate it with:</p><pre>deactivate</pre><p>And that’s it! You’ve successfully set up a Python virtual environment using pyenv. <br>This method gives you precise control over your Python versions without the need for additional tools and with a little better understanding of what is under the hood!</p><p>Any comments, improvements, ideas, suggestions, or criticism? leave me a comment.</p><p>⁰ We assume that we have at least one version of Python installed.</p><p>¹pyenv shell 3.8.8 specifies that 3.8.8 version will be used in the current shell session. Other options are pyenv local 3.8.8, and pyenv global 3.8.8</p><p>² The name can be any of our preference, and the same for whether it is a hidden folder or not.</p><p>³ If the python command is not found means that the alias is not set, after pyenv shell 3.8.8 this should not be the case, but always you can use python3 instead.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=360a3e75efdb" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>