<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jeremy Steward</title>
    <description>The latest articles on Forem by Jeremy Steward (@thatgeoguy).</description>
    <link>https://forem.com/thatgeoguy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F524464%2F99796c42-aebc-4dbc-ade0-3c823f5f2a3e.png</url>
      <title>Forem: Jeremy Steward</title>
      <link>https://forem.com/thatgeoguy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/thatgeoguy"/>
    <language>en</language>
    <item>
      <title>Making Remote Work *Work* With GitLab</title>
      <dc:creator>Jeremy Steward</dc:creator>
      <pubDate>Thu, 18 Feb 2021 21:10:57 +0000</pubDate>
      <link>https://forem.com/tangramvision/making-remote-work-work-with-gitlab-34h0</link>
      <guid>https://forem.com/tangramvision/making-remote-work-work-with-gitlab-34h0</guid>
      <description>&lt;p&gt;&lt;a href="https://www.tangramvision.com"&gt;Tangram Vision&lt;/a&gt; started our mission to make integrating and deploying multi-sensor applications easy in 2020. While starting a new venture is invariably hard, the pandemic has changed many of the ways in which we all work. Notably, our engineering team has been working fully remote from the very beginning. There are a lot of ways in which remote collaboration is made possible, from various tools to the way one’s team interacts. In particular, our engineering team has placed GitLab at the core of our remote workflow, because it reinforces our values and perspectives around working well remotely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zCshQxtA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isv1zjzq9pn3xmw2idz8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zCshQxtA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isv1zjzq9pn3xmw2idz8.jpg" alt="Alt Text"&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@ngilfanov?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Nail Gilfanov&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/computer-forest"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Aspects of Success When Working Remote
&lt;/h2&gt;

&lt;p&gt;“Working remote” is a pretty broad topic. At Tangram Vision, we wanted to list some of the practices we believe promote successfully working remote. In the context of writing software, these are some of the most important high-level considerations for our remote workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mutual sense of trust and understanding&lt;/li&gt;
&lt;li&gt;Asynchronous work-cycle, and&lt;/li&gt;
&lt;li&gt;Division of responsibility&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fostering a &lt;strong&gt;mutual sense of trust and understanding&lt;/strong&gt; is a crucial component to any team. Especially when dealing with large or complex software systems, it is not possible for a single person to keep the entirety of the project in their head at one time. Being able to bring others up to a shared level of understanding about parts of the project they may be unfamiliar with is one way to help foster trust among your teammates. Trust and understanding go hand-in-hand, and lead to a culture where positive collaboration is the default.&lt;/p&gt;

&lt;p&gt;Working &lt;strong&gt;asynchronously&lt;/strong&gt; can seem nerve-wracking if you’re not used to working remotely. However, one of the strongest advantages of working remotely is the flexibility. Schedules are far less constrained (for better or worse) than when working at a fixed location and time. In order to minimize the dependency on needing others to be available to accomplish our goals, we structure our workflow in such a way that information is not lost if something cannot be addressed immediately. This means documenting everything ruthlessly and building a “digital paper trail” of all our work. Through transparency building on top of trust, we are able to make more informed decisions on the urgency and importance of different tasks, and we’re able to operate without blocking on every task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Division of responsibility&lt;/strong&gt; may seem a bit out-of-place on this list but is a crucial part of working remotely and encourages an asynchronous feedback loop. Dividing responsibility allows our team to operate independently, but more important than that is that it encourages us to not build silos. With strong trust as a base, we divide responsibility for documenting, reviewing, and ensuring the quality and spirit of work on the team. While these are all smaller examples of a greater process, the division of responsibility is a crucial part in building sustainable processes.&lt;/p&gt;

&lt;p&gt;In the following sections, we’ll try to demonstrate which features of GitLab contribute towards this remote strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why GitLab? Why Not X?
&lt;/h2&gt;

&lt;p&gt;There are many providers out there for Git hosting. Picking a provider can be difficult since there’s a lot of feature overlap between different providers. However, GitLab as a company operates entirely remotely, and they publish some &lt;a href="https://about.gitlab.com/company/culture/all-remote/guide/"&gt;great guides to working all-remote&lt;/a&gt;. This can be seen in a lot of the tools and workflows they promote. More importantly, GitLab’s own messaging around what aspects and behaviors encourage a strong remote environment echo our own values with regards to remote work.&lt;/p&gt;

&lt;p&gt;Many of the features in the standard GitLab workflow help us express the three aspects of success we listed above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s in a Workflow, Anyway?
&lt;/h2&gt;

&lt;p&gt;There’s probably a million different git workflows out there. Git is extremely flexible and can accommodate a variety of ways to organize work across branches, tags, commits, etc. In many cases a workflow that works well for one team may not work for another. This can be because of any number of these factors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Team size&lt;/li&gt;
&lt;li&gt;Team distribution

&lt;ul&gt;
&lt;li&gt;Do you have separate teams for development &amp;amp; operations, or does your organization rely on some kind of DevOps roles?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Project requirements

&lt;ul&gt;
&lt;li&gt;Safety critical code vs. end-user application&lt;/li&gt;
&lt;li&gt;Device firmware vs. Web application&lt;/li&gt;
&lt;li&gt;Monorepo vs. many small repositories&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Public vs. private work

&lt;ul&gt;
&lt;li&gt;Are you building an open-source project?&lt;/li&gt;
&lt;li&gt;Do you incorporate open-source alongside proprietary code?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Programming language

&lt;ul&gt;
&lt;li&gt;Your choice in language might dictate your continuous integration pipeline, or how you structure the files in your repository&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Many of these questions can dictate different aspects of the process. Unfortunately, there’s no way to immediately make recommendations without knowing your team, but at a high level many of the steps are the same for typical workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitLab Workflows That Works Remotely
&lt;/h2&gt;

&lt;p&gt;At Tangram Vision, we’ve settled into a pretty typical workflow using GitLab’s free offering. Work tends to follow this formula:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Make a new branch to address a specific feature or issue&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This branch is often named according to what you’re working on. We often use a prefix for our branches. e.g. if you’re working on adding a feature to a sub-module in the code, we might call our branch &lt;code&gt;&amp;lt;submodule&amp;gt;/&amp;lt;feature-name&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 This is mostly a convention, so experiment with what works for your team!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new merge request for that branch, titled “WIP: ”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The WIP at the beginning stands for “work in progress.” This signals to others that this branch isn’t ready yet, so don’t start reviewing the branch as the work is incomplete.&lt;/li&gt;
&lt;li&gt;Making the MR early ensures that all your work will have continuous integration (CI) pipelines run on every push.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Work on the branch, committing and pushing code as you go!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the branch is ready for review, we add a reviewer, remove “WIP” from the name of the merge request, and wait for our reviewer to review our code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The reviewer is typically a maintainer on the project, or if you are the only maintainer, someone else on the project who can give your code a second pass.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once the code is reviewed and approved, a maintainer needs to merge it into the default branch!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that’s it. From a surface level, we’re really not doing anything particularly special here. At each step along the way though, there’s a lot to appreciate when working remotely. Let’s get into what that means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Branches
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Having everybody commit directly to your default branch is generally a mistake&lt;/strong&gt;. Using branches breaks up your work into atomic components, and allows for the use of merge requests (discussed in the next section). But what is a default branch anyway? Well, this may be a branch named &lt;code&gt;master&lt;/code&gt;, &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt;, or something else entirely. This is usually a branch that either needs to always be working (e.g. what you ship if you're doing continuous deployment), or is a branch that you cut other releases off of.&lt;/p&gt;

&lt;p&gt;Again, it is generally a mistake to have everyone in your org committing to the same branch at the same time. Doing so quickly devolves into chaos as changes from one team member start propagating to every other team member, and soon others will get frustrated that nothing ever builds.&lt;/p&gt;

&lt;p&gt;In the short term, team members lose trust and confidence in their work since they can’t be sure what changes they’re running every time they pull and then build. Additionally, this hurts understanding, as team members are less likely to review a single commit in isolation, compared to reviewing an entire branch when it comes time for a unit of work to be merged back in. Instead, opt for smaller branches that attempt to solve a single issue (or at least, a group of related issues) at a time. This divides responsibility across the team to be considerate of others working, and helps keep work asynchronous, since changes pushed to branches aren’t going to derail work for the entire team if they cause issues or break builds.&lt;/p&gt;

&lt;p&gt;By using branches we can leverage merge requests, which are an excellent means to collaborate with others. One consequence of remote work is that it can sometimes encourage “lone-wolf” behaviour, where members of the team do not communicate or collaborate at all, and just push more and more work to the repository. Branches and merge requests are at the core of our workflow because they allow us to both enforce and contribute to healthy collaboration among team members.&lt;/p&gt;

&lt;p&gt;How can we enforce this then? Fortunately, GitLab comes with some built-in tools to prevent your team from committing willy-nilly to any branch. The feature is called:&lt;/p&gt;

&lt;h2&gt;
  
  
  Protected Branches
&lt;/h2&gt;

&lt;p&gt;At Tangram Vision, we utilize the protected branches feature of GitLab. What that means is we prevent others from being able to directly push to our default branch at all. You can find this setting in &lt;em&gt;Settings → Repository → Protected Branches&lt;/em&gt; in your repository settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FHQKPukT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2A47tILfDqhfRG4jmXgxIWiA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FHQKPukT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/700/1%2A47tILfDqhfRG4jmXgxIWiA.png" alt="Protected Branches"&gt;&lt;/a&gt;&lt;/p&gt;
Example of protecting the default (main) branch so that only Maintainers can directly push to the main branch, but Developer and Maintainer roles can "merge" a merge request. These permissions have to be set per-repository, but group / organization settings can create defaults.



&lt;p&gt;Protecting branches is one way in which we divide responsibility, by enforcing the use of merge requests to integrate code into the default branch. It’s important to understand roles here, as it is not typically desirable to have everyone in your organization be a maintainer on every repository. As the old saying goes, “the fastest way to starve a horse is to assign two people to feed it.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Merge Requests
&lt;/h2&gt;

&lt;p&gt;Merge requests are one of the most integral tools to our workflow. Merge requests are where we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Review code&lt;/li&gt;
&lt;li&gt;Run automated tests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Merge requests gate code from being merged into our default branch. This effectively means that all code needs to be reviewed and all tests need to be run (and pass) before code makes it back into any kind of release. Let’s look at a few ways that these properties of merge requests help us work better remotely, starting with code review!&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Review
&lt;/h2&gt;

&lt;p&gt;All code at Tangram has to undergo a code review. Responsibility for getting code reviewed is divided across the team. While we usually ask the maintainer of a repository to participate in the code review, maintainers themselves also have to have their code reviewed. This is beneficial for a number of reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reviews are a mechanism for sharing knowledge and fostering understanding between teammates working on the same code base. As the author of a merge request, you have the opportunity to teach others about what approach you took, and why. As the reviewer, you can help contribute to the context surrounding a change, helping build understanding around the decisions made.&lt;/li&gt;
&lt;li&gt;They allow a second pair of eyes to check your work, or suggest next steps.&lt;/li&gt;
&lt;li&gt;They’re one of many mechanisms for seeking dissent. This means understanding concerns and conflicting viewpoints from others affected by the decision. Receiving dissent does not mean that a choice shouldn’t be made; it just needs to be made with consideration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In direct contrast, some things code reviews are not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Are not&lt;/strong&gt; for criticizing code style. Formatters and linters exist to do this job consistently for an entire codebase; these tools should be relied on and deployed automatically, rather than relying on a reviewer’s subjective style preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are not&lt;/strong&gt; a tool for rejecting changes. The focus should always be on an attitude of finding solutions and improving the end result of the code. A healthy attitude can go a long way towards empowering others and making them feel like their contributions are not only welcome but desired. The purpose is to build trust and understanding, not to shoot down ideas for improvement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code reviews are one of the primary ways in which a remote team gets to collaborate, so the spirit of our reviews is really important. When done correctly, code reviews help promote understanding of the code being reviewed, while building stronger trust in the systems we’re designing since we always have (at least) a second pair of eyes on every change. They also are one way in which we divide responsibility, since everyone on the team has to participate in having their code reviewed.&lt;/p&gt;

&lt;p&gt;Lastly, by doing all code reviews in the merge request itself, we build a paper trail of documentation for the future. This means that we can look back at exactly when a change was introduced, and find any discussion about potential trade-offs next to a change. This gives the team confidence in understanding the context behind a change, months or years after it has been introduced. Moreover, it encourages team members to be able to work asynchronously, as that context is not held in any single individual’s head, but instead written and made explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated CI and Testing
&lt;/h2&gt;

&lt;p&gt;There’s probably enough here we could write a whole blog post on its own, but suffice to say we set up our own GitLab CI runners that can be hosted on DigitalOcean or Amazon infrastructure. The number of free minutes that come with GitLab’s free offering are pretty limited so having our own runners is pretty crucial to having CI.&lt;/p&gt;

&lt;p&gt;Code reviews do not need to focus on determining which tests are broken as a result of some work (if any), because the CI pipeline does this for us. Responsibility for ensuring that the pipeline is in a good state is divided across the team. By keeping the pipeline in a good state, we can build trust in the state of the code-base, which is essential when beginning new work.&lt;/p&gt;

&lt;p&gt;One thing that isn’t required, but is helpful, is making work-in-progress (WIP) merge requests before a branch is complete and ready for review. The advantage of these is that our CI pipeline runs in the background on every push to Gitlab. This is useful even if you’re not working remotely, but means that you get early feedback on your changes from CI before you submit it for full review. This helps decouple the feedback process from development a little bit, making the whole workflow a bit more asynchronous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Committing Work to Your Branch
&lt;/h2&gt;

&lt;p&gt;There’s little to say here since this will be largely dictated by the individual contributors themselves. One thing to keep in mind is keeping a clean and consistent history through your commit messages. This &lt;a href="https://chris.beams.io/posts/git-commit/"&gt;excellent article by Chris Beams&lt;/a&gt; offers more on the topic than we can add ourselves. Here at Tangram Vision, this article helps define our own goalposts for what makes a good commit message.&lt;/p&gt;

&lt;p&gt;Commit messages are another great way to help communicate intent across a remote team, and serve as a form of documentation on “why” changes were made. This is another reason we don’t mandate that changes are squashed before merging, as the history helps contextualize how the code grows and changes over time. Overall, this is another way in which we make our context explicit through documenting the process. This builds trust and understanding across the team, and encourages a more asynchronous workflow as team members are encouraged to read the git log to understand changes made to the system!&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing This All Together
&lt;/h2&gt;

&lt;p&gt;Our general workflow isn’t terribly complex, and represents a fairly standard workflow on GitLab’s free offering. Despite this, we can see how GitLab’s default workflow and many of the settings available are geared towards encouraging remote work and making it easier.&lt;/p&gt;

&lt;p&gt;GitLab is one way in which Tangram is building a remote-first workplace. We lean on trust, an asynchronous workflow, and dividing responsibility across the organization to remote software development a success. Whether it is protecting branches, encouraging the use of merge requests, running CI pipelines or just keeping a collaborative spirit around code review, GitLab helps us do our jobs every day.&lt;/p&gt;

&lt;p&gt;As always, feel free to reach out to us through our &lt;a href="https://www.tangramvision.com"&gt;website&lt;/a&gt; or on &lt;a href="https://twitter.com/TangramVision"&gt;Twitter&lt;/a&gt; if you have something to add! What are some ways in which you or your organization use GitLab to help foster better remote work?&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>ci</category>
      <category>remote</category>
      <category>devops</category>
    </item>
    <item>
      <title>Rotate, Scale, Translate: Coordinate frames for multi-sensor systems - Part 1</title>
      <dc:creator>Jeremy Steward</dc:creator>
      <pubDate>Wed, 20 Jan 2021 22:42:29 +0000</pubDate>
      <link>https://forem.com/tangramvision/rotate-scale-translate-coordinate-frames-for-multi-sensor-systems-54e9</link>
      <guid>https://forem.com/tangramvision/rotate-scale-translate-coordinate-frames-for-multi-sensor-systems-54e9</guid>
      <description>&lt;p&gt;&lt;strong&gt;Update!&lt;/strong&gt; You can find Part 2, where we explore coordinate frames for &lt;em&gt;3D systems&lt;/em&gt;, &lt;a href="https://www.tangramvision.com/blog/rotate-scale-translate-coordinate-frames-for-multi-sensor-systems-part-2"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Multi-sensor systems are becoming ever more common as we seek to automate robotics, navigation, or create a "smart" world. Any random assortment of sensors could be sensing any number of quantities, from barometric pressure, to temperature, to even more complex sensors that scan the world around us and produce 3D maps (like LiDAR). These multi-sensor systems often carry a great deal of complexity, to be able to learn about the world to the same degree that we can just by listening, feeling, and seeing our environment.&lt;/p&gt;

&lt;p&gt;An important aspect of multi-sensor systems is how we relate these assorted sensing platforms together. If we are reading temperature, how do we use that value to make decisions? If we have multiple cameras, how can we tell if an object moves from the view of one camera to the next? Individual sensors on their own may be insufficient for making decisions, and therefore need to be linked together. There are a handful of ways we can correlate data, but one of the most powerful ways is to do so &lt;em&gt;spatially&lt;/em&gt;. In this post, we're going to explore some of the language and tools we use for doing so.&lt;/p&gt;

&lt;h1&gt;
  
  
  Location, location, location
&lt;/h1&gt;

&lt;p&gt;It's all about location. No, really. All sensors have some spatial property, and it is by relating sensors together in this way that we can produce useful results! Knowing that a sensor measured 30° C isn't particularly useful on its own. Knowing that a sensor &lt;em&gt;in your thermostat&lt;/em&gt; measured 30° C is a much more useful distinction. The same holds true for more sophisticated sensors such as cameras, LiDAR, accelerometers, etc. These "advanced" sensors are even measuring spatial quantities, which are the backbone of modern robotics and automation. &lt;/p&gt;

&lt;p&gt;Many of the useful aspects of multi-sensor systems are derived by a spatial understanding of the world around us. Location helps us decide how related any two quantities might be; rather, it is in relating things spatially that our sensors can derive the context of the world around us. Many of the problems in integrating new sensors into a robotics or automation system are therefore coordinate system problems. To understand this, we first need to talk about &lt;em&gt;coordinate frames&lt;/em&gt; and then discuss how to &lt;em&gt;relate&lt;/em&gt; any two coordinate frames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensors and coordinate frames
&lt;/h2&gt;

&lt;p&gt;A coordinate frame or coordinate system is a way for us to label the positions of some points using a set of coordinates, relative to the system's origin. A common type of coordinate frame is a Cartesian coordinate frame, which labels positions along a set of perpendicular axes. Consider the following Cartesian grid, defining a coordinate frame:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XmG_az8u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AooEfJFcDsa769TKCoHFEgA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XmG_az8u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AooEfJFcDsa769TKCoHFEgA.png" alt="Figure 1"&gt;&lt;/a&gt;&lt;/p&gt;
An example of a Cartesian grid with three points (p, q, r) plotted within. This grid represents a coordinate frame or coordinate system with an origin O and two perpendicular axes x and y. 



&lt;p&gt;For a Cartesian frame, we denote the position of our points as a &lt;a href="https://en.wikipedia.org/wiki/Tuple"&gt;tuple&lt;/a&gt; with the offset from the origin &lt;em&gt;O&lt;/em&gt; along each of the axes. In the above example, our point &lt;em&gt;p&lt;/em&gt; has a coordinate of &lt;em&gt;(2, 2)&lt;/em&gt;, while &lt;em&gt;q&lt;/em&gt; has a coordinate of &lt;em&gt;(3, 4)&lt;/em&gt;. For any point in the system, you can describe its coordinate as &lt;em&gt;(xp, yp)&lt;/em&gt; for a point &lt;em&gt;p&lt;/em&gt;, where &lt;em&gt;xp&lt;/em&gt; is the offset from the origin &lt;em&gt;O&lt;/em&gt; along the direction of the &lt;em&gt;x&lt;/em&gt;-axis and &lt;em&gt;yp&lt;/em&gt; is the offset from the origin &lt;em&gt;O&lt;/em&gt; along the direction of the &lt;em&gt;y&lt;/em&gt;-axis.&lt;/p&gt;

&lt;p&gt;Other types of coordinate frames exist as well, including &lt;a href="https://mathworld.wolfram.com/PolarCoordinates.html"&gt;polar coordinate systems&lt;/a&gt; and &lt;a href="https://mathworld.wolfram.com/SphericalCoordinates.html"&gt;spherical coordinate systems&lt;/a&gt;. Map projections such as &lt;a href="https://en.wikipedia.org/wiki/Mercator_projection"&gt;Mercator&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Gnomonic_projection"&gt;Gnomonic&lt;/a&gt; maps are also a type of projected coordinate system, that exist as a handy way to plot and interpret the same data in different ways. For now however, we will focus on Cartesian coordinate frames, as they tend to be the most commonly used coordinate frame to represent and interpret spatial data. &lt;/p&gt;

&lt;p&gt;From the example above, we can pick out a few salient components of our Cartesian frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;O&lt;/em&gt;: The origin of our coordinate frame. This tells us to what point every point is relative. Every point is relative to the origin, and the origin can be defined as a point that has zero offset relative to itself.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;x&lt;/em&gt;- and &lt;em&gt;y&lt;/em&gt;-axes: Every coordinate frame will have a number of axes used to describe the various dimensions of the coordinate system. For now, we're sticking with 2-dimensional (2D) data, so we'll stick to labeling our axes as the &lt;em&gt;x&lt;/em&gt;- and &lt;em&gt;y&lt;/em&gt;-axes. These could actually be called anything, like the &lt;em&gt;a&lt;/em&gt;- and &lt;em&gt;b&lt;/em&gt;-axes, but the typical convention for Cartesian coordinate frames is to name them &lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Axes order: oftentimes you'll hear about left-handed vs. right-handed coordinate systems. There are ways to distinguish the difference, but we're choosing to gloss over that for now in this primer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Typically, a Cartesian frame is represented as a "right-handed" coordinate frame. The distinction isn't super important, and the idea of left vs. right-handed frames can be extremely confusing at first. More often than not, you won't see a left-handed Cartesian coordinate system that uses &lt;em&gt;x&lt;/em&gt; / &lt;em&gt;y&lt;/em&gt; / &lt;em&gt;z&lt;/em&gt; terminology. Moreover, right-handed frames are more-or-less the standard for conventional robotics sensing. All you need to know is that for what we're doing here we will be representing all our math for right-handed, Cartesian frames.&lt;/p&gt;

&lt;p&gt;In order to spatially relate our sensors together, we need to give each sensor its own local coordinate frame. For a camera, this might be where the camera lens is centered over the image plane, or what we call the principal point. For an accelerometer, this may be the centre of where accelerations are read. Regardless of where this coordinate frame is defined though, we need to have a "local" frame for each of these sensors so that we can relate these together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "world frame"
&lt;/h2&gt;

&lt;p&gt;When relating multiple coordinate frames together, it is often helpful to visualize a "world" frame. A world frame is a coordinate frame that describes the "world," which is a space that contains all other coordinate frames we care about. The world frame is typically most useful in the context of spatial data, where our "world" may be the real space we inhabit. Some common examples of "world" frames that get used in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your automated vehicle platform might reference all cameras (local systems) to an on-board inertial measurement unit (IMU). The IMU may represent the "world" frame for that vehicle.&lt;/li&gt;
&lt;li&gt;A mobile-mapping system may incorporate cameras and LiDAR to collect street-view imagery. The LiDAR system, which directly measures points, may be treated as your world frame.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the world frame, we can co-locate and relate points from different local systems together. See the below figure, which shows how a point &lt;em&gt;p&lt;/em&gt; in the world frame can be related between multiple other frames, e.g. A and B. While we may only know the coordinates for a point in one frame, we eventually want to be able to express the coordinates of that point in other coordinate frames as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--geBZAsDw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AC1IBS4Fwt3aY3oZxC-9fng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--geBZAsDw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AC1IBS4Fwt3aY3oZxC-9fng.png" alt="Figure 2"&gt;&lt;/a&gt;&lt;/p&gt;
A world frame, denoted by the orange and teal axes with origin OW. This world frame contains two "local" coordinate frames A and B, with origins OA and OB. A common point p is also plotted. This point exists in all three coordinate frames, but is only listed with a coordinate for pW, which is the point's coordinates in the world frame.



&lt;p&gt;The world frame as shown in the figure above is useful as it demonstrates a coordinate frame in which we can reference the coordinates of &lt;em&gt;OA&lt;/em&gt; and &lt;em&gt;OB&lt;/em&gt; relative to the world frame. All coordinate systems are relative to some position; however, if &lt;em&gt;OA&lt;/em&gt; and &lt;em&gt;OB&lt;/em&gt; are only relative to themselves, we have no framework with which to relate them. So instead, we relate them within our world frame, which helps us visualize the differences between our coordinate systems A and B.&lt;/p&gt;

&lt;h1&gt;
  
  
  Relating two frames together
&lt;/h1&gt;

&lt;p&gt;In the previous figure, we showed off two local frames A &amp;amp; B inside our world frame W. An important aspect in discussing coordinate frames is establishing a consistent notation in how we refer to coordinate frames and their relationships. Fortunately, mathematics comes to the rescue here as it provides some tools to help us derive a concise way of relating coordinate frames together.&lt;/p&gt;

&lt;p&gt;Let's suppose we still have two coordinate frames we care about, namely coordinate frame A and coordinate frame B. We have a coordinate in frame A, &lt;em&gt;pA&lt;/em&gt;, that we want to know the location of in frame B (i.e. we are searching for &lt;em&gt;pB&lt;/em&gt;). We know that this point can exist in both frames, and therefore there is a functional &lt;em&gt;transformation&lt;/em&gt; to convert to &lt;em&gt;pB&lt;/em&gt; from &lt;em&gt;pA&lt;/em&gt;. In mathematics, we express relations as a function:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;pB=f(pA)p_B = f(p_A) &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathdefault"&gt;p&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathdefault mtight"&gt;B&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathdefault"&gt;f&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathdefault"&gt;p&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathdefault mtight"&gt;A&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;We don't know what this function &lt;em&gt;f&lt;/em&gt; is yet, but we will define it soon! It's enough to know that we are getting a point in coordinate frame B from coordinate frame A. The function &lt;em&gt;f&lt;/em&gt; is what we typically call a &lt;em&gt;transform&lt;/em&gt;. However, saying "the coordinate frame B from coordinate frame A transform" is a bit wordy. We tend to lean towards referring to this transform as the B from A transform, or B←A transform for brevity.&lt;/p&gt;

&lt;p&gt;Notice the direction here is not A &lt;strong&gt;to&lt;/strong&gt; B, but rather B &lt;strong&gt;from&lt;/strong&gt; A. While this is a bit of a semantic distinction, the latter is preferred here because of how it is later expressed mathematically. We will eventually define a &lt;em&gt;transformation matrix&lt;/em&gt;, Γ&lt;sup&gt;B&lt;/sup&gt;A, that describes the transform &lt;em&gt;f&lt;/em&gt; above. The superscript and subscript are conventionally written this way, denoting the B←A relationship. Keeping many coordinate frames consistent in your head can be difficult enough, so consistency in our notation and language will help us keep organized.&lt;/p&gt;

&lt;p&gt;Now that we have a notation and some language to describe the relationship between two coordinate frames, we just need to understand what kinds of transforms we can apply between two coordinate frames. Fortunately, there's only 3 categories of transformations that we need to care about: &lt;em&gt;translations&lt;/em&gt;, &lt;em&gt;rotations&lt;/em&gt;, and &lt;em&gt;scale&lt;/em&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Translation
&lt;/h2&gt;

&lt;p&gt;Translations are the easiest type of coordinate transform to understand. In fact, we've already taken translation for granted when defining points in a Cartesian plane as offsets from the origin. Given two coordinate systems A and B, a translation between the two might look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c_OU303v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A5jion3RLwwL3kqdWz43IzQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c_OU303v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A5jion3RLwwL3kqdWz43IzQ.png" alt="Figure 3"&gt;&lt;/a&gt;&lt;/p&gt;
A world frame that shows two local frames, A and B, which are related to each other by a translation. This translation, T&lt;sup&gt;B&lt;/sup&gt;A, is just a set of linear offsets from one coordinate frame to the other. 



&lt;p&gt;Mathematically, we might represent this as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hx2yOzL0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2Axc7t748g1kMaSIJvjvW8jQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hx2yOzL0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2Axc7t748g1kMaSIJvjvW8jQ.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--22GVU6Zs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AI9CFYn4bSsCQ7shZzFu9GQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--22GVU6Zs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AI9CFYn4bSsCQ7shZzFu9GQ.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VBMTgK2O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AhKj2nhQzUMDM_eMNKZaK1Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VBMTgK2O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AhKj2nhQzUMDM_eMNKZaK1Q.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty simple addition, which makes this type of transformation easy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotation
&lt;/h2&gt;

&lt;p&gt;Rotations are probably the most complex type of transform to deal with. These transforms are not fixed offsets like translations, but rather vary based on your distance from the origin of your coordinate frame. Given two coordinate frames A and B, a rotation might look like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zQn246r7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AJAYQNV6gboWDsr6s0V0QyA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zQn246r7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AJAYQNV6gboWDsr6s0V0QyA.png" alt="Figure 4"&gt;&lt;/a&gt;&lt;/p&gt;
A world frame that shows two local frames, A and B, which are related to each other by a rotation. This rotation, R&lt;sup&gt;B&lt;/sup&gt;A, is a linear transformation that depends on the angle of rotation, θ



&lt;p&gt;In the 2D scenario like shown above, we only have a single plane (the &lt;em&gt;XY&lt;/em&gt;-plane), so we only need to worry about a single rotation. Mathematically, we would represent this as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G_lNRfAW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ArmgSYIl2cSC49-65cjbXtw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G_lNRfAW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ArmgSYIl2cSC49-65cjbXtw.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This rotation matrix assumes that positive rotations are counter-clockwise. This is what is often referred to as the &lt;a href="https://en.wikipedia.org/wiki/Right-hand_rule#Curve_orientation_and_normal_vectors"&gt;right-hand rule&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To get a point &lt;em&gt;pB&lt;/em&gt; from &lt;em&gt;pA&lt;/em&gt; we then do:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HFOW8Kaz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2APOP8NmyUxx144ptmBme6Gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HFOW8Kaz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2APOP8NmyUxx144ptmBme6Gw.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eQu73R67--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AUDGTe_qaH4dieinyOAnxlQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eQu73R67--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AUDGTe_qaH4dieinyOAnxlQ.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OqrDXR1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AMqViXbJxjN9wAnctHIAHCA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OqrDXR1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AMqViXbJxjN9wAnctHIAHCA.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The matrix multiplication is a bit more involved this time, but remains quite straightforward. Fortunately this is the most difficult transformation to formulate, so we're almost there! &lt;/p&gt;

&lt;h2&gt;
  
  
  Scale
&lt;/h2&gt;

&lt;p&gt;The final type of transformation we need to consider is scale. Consider the following diagram, similar to the ones we've shown thus far:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sPa2TxJy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AkEEXEaJVfGcs5hZcVDC-BA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sPa2TxJy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AkEEXEaJVfGcs5hZcVDC-BA.png" alt="Figure 5"&gt;&lt;/a&gt;&lt;/p&gt;
A world frame that shows two local frames, A and B, which are related to each other by both a translation and scale. The translation exists to compare A and B side-by-side, but the difference in axes lengths between the A and B frames is a visual trick used to demonstrate what a scale factor might look like, assuming that two coordinate frames are compared in reference to a world frame.



&lt;p&gt;The two frames are again translated, but this is not important for what we're looking at here. Notice that the axes of A are a different length than the axes of B. This is a visual trick to demonstrate what scale transformations do between two coordinate frames. An example of a real-world scale issue might be a unit conversion. Namely, B might be in units of meters, while A is in units of millimeters. This scale difference will change the final results of any point &lt;em&gt;pB&lt;/em&gt; relative to &lt;em&gt;pA&lt;/em&gt;, by a multiplicative factor. If we have an isometric scale, we might represent this mathematically as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1psoJNjI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AftCQu6jGTVm-EfSaRKJJwA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1psoJNjI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AftCQu6jGTVm-EfSaRKJJwA.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ni88vwnr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AA-0YpqgsdzqjqZeqFtyJzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ni88vwnr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AA-0YpqgsdzqjqZeqFtyJzw.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, in this way, we are using a scalar value to represent an isometric scale across both &lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; axes. This is not always the case, as sometimes our scale is not isometric. In robotics, we typically treat most of our sensors as having an isometric scale, but it is worth showing the mathematics for how one might generalize this if the scale in &lt;em&gt;x&lt;/em&gt; &lt;em&gt;(sx)&lt;/em&gt; is different from the scale in &lt;em&gt;y&lt;/em&gt; &lt;em&gt;(sy)&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vyfl-QW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AqZBjj20gLIQgfsx2jNorUA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vyfl-QW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AqZBjj20gLIQgfsx2jNorUA.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ni88vwnr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AA-0YpqgsdzqjqZeqFtyJzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ni88vwnr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AA-0YpqgsdzqjqZeqFtyJzw.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PLjcO1uy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIMwrf0xwmw1-W5ZAqXX6LA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PLjcO1uy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIMwrf0xwmw1-W5ZAqXX6LA.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By utilizing this more general matrix equation over the scalar form above, it is easy to abstract between isometric scales (where &lt;em&gt;sx = sy&lt;/em&gt;), and affine transforms. Fortunately it's often very easy to get away with assuming that our scale is isometric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Now that we know the three types of transforms, how do we put it all together? Any two coordinate frames A and B could have any number of translations, rotations, and scale factors between them. A transform in the general sense incorporates all three of these operations, and so we need a more formal way to represent them. Using our previous notation, we can formulate it as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F531J_u9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AwllLmnaamS6OQ1sYujU7Wg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F531J_u9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AwllLmnaamS6OQ1sYujU7Wg.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ASJr7lTH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AanoGuPA4YMarpmuOWcDLRw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ASJr7lTH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AanoGuPA4YMarpmuOWcDLRw.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iaWhba31--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ANudcswLmzjTKTLv5sI_22Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iaWhba31--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2ANudcswLmzjTKTLv5sI_22Q.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind the order here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rotation&lt;/li&gt;
&lt;li&gt;Scale&lt;/li&gt;
&lt;li&gt;Translation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With all the multiplications going on, this can get very confusing very quickly! Moreover, remembering the order every time can be pretty difficult. To make this more consistent, mathematicians and engineers often try to represent it as a single matrix multiplication. This way, the order is never confusing. We call this single matrix Γ&lt;sup&gt;B&lt;/sup&gt;A. In plain English, we typically call this the B from A transformation matrix, or just the B←A transform. Unfortunately, however, you'll notice that not every operation in our above equation is a matrix multiplication, so the following doesn't work!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eqWmghc7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIFFocquT4U9WUwe6h_Ox3A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eqWmghc7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIFFocquT4U9WUwe6h_Ox3A.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This would be fine for rotation and scale, but doesn't allow us to do anything about translations. Fortunately, we can work around this somewhat by leveraging a small trick of mathematics: increasing the dimensionality of our problem. If we change some of our definitions around, we can create a nuisance or &lt;em&gt;dummy&lt;/em&gt; dimension that allows us to formulate Γ&lt;sup&gt;B&lt;/sup&gt;A as:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QhQKAnzA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2Ac3MKnKQEUtjGUKIfQTN1Xg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QhQKAnzA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2Ac3MKnKQEUtjGUKIfQTN1Xg.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pu7fS5Jw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A_FwCxrQNNfRw7NW80eK1lg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pu7fS5Jw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2A_FwCxrQNNfRw7NW80eK1lg.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eqWmghc7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIFFocquT4U9WUwe6h_Ox3A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eqWmghc7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AIFFocquT4U9WUwe6h_Ox3A.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JQv4DQ3U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AOsrSeimXmdJ67reLd8DB9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JQv4DQ3U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/800/1%2AOsrSeimXmdJ67reLd8DB9w.png" alt="Equation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that our last dimension on each point remains equal to 1 on both sides of the transformation (this is our nuisance dimension)! Additionally, the last row of Γ&lt;sup&gt;B&lt;/sup&gt;A is always zeros, except for the value in the bottom right corner of the matrix, which is also a 1!&lt;/p&gt;

&lt;p&gt;If you want to learn more about the trick we're applying above, search for &lt;em&gt;homogeneous coordinates&lt;/em&gt; or &lt;em&gt;projective coordinates&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;Try it for yourself with these Python functions! Find this and the code used to generate our above figures in our &lt;a href="https://github.com/Tangram-Vision/Tangram-Vision-Blog"&gt;Tangram Visions Blog repository&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Tangram-Vision"&gt;
        Tangram-Vision
      &lt;/a&gt; / &lt;a href="https://github.com/Tangram-Vision/Tangram-Vision-Blog"&gt;
        Tangram-Vision-Blog
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Code pertaining to posts made on our official blog!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Tangram Visions Blog&lt;/h1&gt;
&lt;p&gt;This repo holds code to generate the assets used in the company blog for Tangram Vision! The main page of the blog can be &lt;a href="https://www.tangramvision.com/blog" rel="nofollow"&gt;found here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See the README in each directory for instructions on installation, operation, and output.&lt;/p&gt;
&lt;h2&gt;
Table of Contents&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;One to Many Sensors&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code &lt;a href="https://raw.githubusercontent.com/Tangram-Vision/Tangram-Vision-Blog/main/2020.11.30_OneToManySensors"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2020.11.30 post: &lt;a href="https://www.tangramvision.com/blog/one-to-many-sensor-trouble-part-1" rel="nofollow"&gt;One To Many Sensors, Part I&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2020.12.04 post: &lt;a href="https://www.tangramvision.com/blog/one-to-many-sensor-trouble-part-2" rel="nofollow"&gt;One To Many Sensors, Part II&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Coordinate Frames&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code &lt;a href="https://raw.githubusercontent.com/Tangram-Vision/Tangram-Vision-Blog/main/2021.01.21_CoordinateFrames"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2021.01.21 post: &lt;a href="https://www.tangramvision.com/blog/coordinate-systems-and-how-to-relate-multiple-coordinate-frames-together-part-1" rel="nofollow"&gt;Coordinate systems, and how to relate multiple coordinate frames together
Part I&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Exploring Ansible via Setting Up a WireGuard VPN&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code &lt;a href="https://raw.githubusercontent.com/Tangram-Vision/Tangram-Vision-Blog/main/2021.03.04_AnsibleVpnSetup"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2021.03.04 post: &lt;a href="https://www.tangramvision.com/blog/exploring-ansible-via-setting-up-a-wireguard-vpn" rel="nofollow"&gt;Exploring Ansible via Setting Up a WireGuard
VPN&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Color (Or Not)&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code &lt;a href="https://raw.githubusercontent.com/Tangram-Vision/Tangram-Vision-Blog/main/2021.03.00_ColorOrNot"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2021.03.?? post: &lt;a href="https://www.tangramvision.com/blog/" rel="nofollow"&gt;Color (Or Not)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Contributing to this project&lt;/h2&gt;
&lt;p&gt;This is an open-source project; if you'd like to contribute a feature or adapt the code, feel free! We suggest you check
out our &lt;a href="https://raw.githubusercontent.com/Tangram-Vision/Tangram-Vision-Blog/main/CONTRIBUTING.md"&gt;contributing guidelines&lt;/a&gt;. After that, make…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Tangram-Vision/Tangram-Vision-Blog"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  What was it all for?
&lt;/h1&gt;

&lt;p&gt;Knowing how to express relationships between coordinate frames both in plain English (I want the B←A &lt;em&gt;or&lt;/em&gt; B from A transformation) and in mathematics (I want Γ&lt;sup&gt;B&lt;/sup&gt;A) helps bridge the gap for how we relate sensors to each other. In a more concrete example, suppose we have two cameras: depth and color. We might want depth←color, so that we can fuse semantic information from the color camera with spatial information from the depth camera. Eventually, we want to then relate that information back to real world coordinates (e.g. I want world←depth). &lt;/p&gt;

&lt;p&gt;Coordinate frames and coordinate systems are a key component to integrating multi-sensor frameworks into robotics and automation projects. Location is of the utmost importance, even more when we consider that many robotics sensors are spatial in nature. Becoming an expert in coordinate systems is a path towards a stable, fully-integrated sensor suite on-board your automated platform of choice. &lt;/p&gt;

&lt;p&gt;While these can be fascinating challenges to solve while creating a multi-sensor-equipped system like a robot, they can also become unpredictably time consuming, which can delay product launches and feature updates. The &lt;a href="https://www.tangramvision.com"&gt;Tangram Vision SDK&lt;/a&gt; includes tools and systems to make this kind of work more streamlined and predictable — and it's free to trial, too. &lt;/p&gt;

&lt;p&gt;We hope you found this article helpful—if you've got any feedback, comments or questions, be sure to &lt;a href="https://www.twitter.com/tangramvision"&gt;tweet at us&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>computervision</category>
      <category>sensors</category>
      <category>perception</category>
    </item>
  </channel>
</rss>
