{"id":153,"date":"2025-12-30T17:02:24","date_gmt":"2025-12-30T17:02:24","guid":{"rendered":"https:\/\/metavilabs.com\/blog\/?p=153"},"modified":"2025-12-30T17:02:24","modified_gmt":"2025-12-30T17:02:24","slug":"a-free-tool-for-understanding-and-reproducing-ibidi-chemotaxis-plots","status":"publish","type":"post","link":"https:\/\/metavilabs.com\/blog\/2025\/12\/30\/a-free-tool-for-understanding-and-reproducing-ibidi-chemotaxis-plots\/","title":{"rendered":"A Free Tool for Understanding (and Reproducing) ibidi Chemotaxis Plots"},"content":{"rendered":"<div class=\"boldgrid-section\">\n<div class=\"container\">\n<div class=\"row\">\n<div class=\"col-lg-12 col-md-12 col-xs-12 col-sm-12\">\n<p data-start=\"421\" data-end=\"509\">&nbsp;<\/p>\n<p class=\"\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-157\" src=\"https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/ibidi_plot-300x252.png\" alt=\"\" width=\"300\" height=\"252\" srcset=\"https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/ibidi_plot-300x252.png 300w, https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/ibidi_plot-1024x859.png 1024w, https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/ibidi_plot-768x644.png 768w, https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/ibidi_plot.png 1240w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-159\" src=\"https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/which_side_is_the_chamber-300x259.png\" alt=\"\" width=\"300\" height=\"259\" srcset=\"https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/which_side_is_the_chamber-300x259.png 300w, https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/which_side_is_the_chamber-1024x885.png 1024w, https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/which_side_is_the_chamber-768x664.png 768w, https:\/\/metavilabs.com\/blog\/wp-content\/uploads\/2025\/12\/which_side_is_the_chamber.png 1134w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<p class=\"\" data-start=\"421\" data-end=\"509\">download:&nbsp; &nbsp; &nbsp;&nbsp; <a href=\"https:\/\/metavilabs.com\/sites\/default\/files\/share\/ibidi_plot_python.zip\">https:\/\/metavilabs.com\/sites\/default\/files\/share\/ibidi_plot_python.zip<\/a><\/p>\n<p class=\"\" data-start=\"421\" data-end=\"509\"><strong data-start=\"421\" data-end=\"509\">Chemotaxis experiments are conceptually simple \u2014 but the coordinate systems are not.<\/strong><\/p>\n<p class=\"\" data-start=\"511\" data-end=\"535\">If you\u2019ve ever tried to:<\/p>\n<ul class=\"\" data-start=\"536\" data-end=\"718\">\n<li data-start=\"536\" data-end=\"596\">\n<p data-start=\"538\" data-end=\"596\">reproduce ibidi\u2019s chemotaxis plots outside their software,<\/p>\n<\/li>\n<li data-start=\"597\" data-end=\"640\">\n<p data-start=\"599\" data-end=\"640\">validate migration direction numerically,<\/p>\n<\/li>\n<li data-start=\"641\" data-end=\"718\">\n<p data-start=\"643\" data-end=\"718\">or explain to a colleague why \u201cdown in the image\u201d becomes \u201cup in the plot\u201d,<\/p>\n<\/li>\n<\/ul>\n<p data-start=\"720\" data-end=\"799\">you\u2019ve probably discovered that <strong data-start=\"752\" data-end=\"798\">coordinate systems are the real experiment<\/strong>.<\/p>\n<p data-start=\"801\" data-end=\"1054\">At MetaVi Labs, we recently ran into this exact problem while validating automated chemotaxis analysis. The result is a <strong data-start=\"921\" data-end=\"947\">free, open Python tool<\/strong> that reproduces ibidi-style plots <em data-start=\"982\" data-end=\"993\">correctly<\/em> \u2014 and, more importantly, makes the underlying math explicit.<\/p>\n<p class=\"\" data-start=\"1056\" data-end=\"1163\">This post explains <strong data-start=\"1075\" data-end=\"1115\">why ibidi plots look the way they do<\/strong>, and introduces a tool you can use (and trust).<\/p>\n<h2 data-start=\"1170\" data-end=\"1220\">The core confusion: image space vs. ibidi space<\/h2>\n<p data-start=\"1222\" data-end=\"1305\">ibidi track exports (<code data-start=\"1243\" data-end=\"1261\">ibidi_tracks.txt<\/code>) are stored in <strong data-start=\"1277\" data-end=\"1304\">image pixel coordinates<\/strong>:<\/p>\n<ul data-start=\"1307\" data-end=\"1375\">\n<li data-start=\"1307\" data-end=\"1342\">\n<p data-start=\"1309\" data-end=\"1342\">Origin: <strong data-start=\"1317\" data-end=\"1342\">top-left of the image<\/strong><\/p>\n<\/li>\n<li data-start=\"1343\" data-end=\"1359\">\n<p data-start=\"1345\" data-end=\"1359\"><strong data-start=\"1345\" data-end=\"1351\">+X<\/strong> \u2192 right<\/p>\n<\/li>\n<li data-start=\"1360\" data-end=\"1375\">\n<p data-start=\"1362\" data-end=\"1375\"><strong data-start=\"1362\" data-end=\"1368\">+Y<\/strong> \u2192 down<\/p>\n<\/li>\n<\/ul>\n<p class=\"\" data-start=\"1377\" data-end=\"1460\">This is standard for images \u2014 but <strong data-start=\"1411\" data-end=\"1418\">not<\/strong> for math, physics, or plotting libraries.<\/p>\n<p data-start=\"1462\" data-end=\"1521\">ibidi, however, does <strong data-start=\"1483\" data-end=\"1520\">not analyze tracks in image space<\/strong>.<\/p>\n<p data-start=\"1523\" data-end=\"1588\">Instead, it constructs a <strong data-start=\"1548\" data-end=\"1587\">gradient-relative coordinate system<\/strong>:<\/p>\n<ul data-start=\"1590\" data-end=\"1754\">\n<li data-start=\"1590\" data-end=\"1637\">\n<p data-start=\"1592\" data-end=\"1637\"><strong data-start=\"1592\" data-end=\"1606\">+Y (ibidi)<\/strong> = <em data-start=\"1609\" data-end=\"1637\">toward the chemoattractant<\/em><\/p>\n<\/li>\n<li data-start=\"1638\" data-end=\"1688\">\n<p data-start=\"1640\" data-end=\"1688\"><strong data-start=\"1640\" data-end=\"1654\">+X (ibidi)<\/strong> = <em data-start=\"1657\" data-end=\"1688\">perpendicular to the gradient<\/em><\/p>\n<\/li>\n<li data-start=\"1689\" data-end=\"1754\">\n<p data-start=\"1691\" data-end=\"1754\"><strong data-start=\"1691\" data-end=\"1700\">(0,0)<\/strong> = a <em data-start=\"1705\" data-end=\"1729\">cell-defined reference<\/em> (not a chamber location)<\/p>\n<\/li>\n<\/ul>\n<p data-start=\"1756\" data-end=\"1766\">Crucially:<\/p>\n<blockquote data-start=\"1768\" data-end=\"1851\">\n<p data-start=\"1770\" data-end=\"1851\"><strong data-start=\"1770\" data-end=\"1805\">The reservoir is never plotted.<\/strong><br data-start=\"1805\" data-end=\"1808\">It exists conceptually \u201csomewhere in +Y\u201d.<\/p>\n<\/blockquote>\n<p class=\"\" data-start=\"1853\" data-end=\"1922\">Once you realize this, most of the apparent contradictions disappear.<\/p>\n<h2 data-start=\"1929\" data-end=\"1971\">Why plots flip, rotate, or \u201clook wrong\u201d<\/h2>\n<p data-start=\"1973\" data-end=\"2009\">Here\u2019s a common real-world scenario:<\/p>\n<ul data-start=\"2011\" data-end=\"2170\">\n<li data-start=\"2011\" data-end=\"2076\">\n<p data-start=\"2013\" data-end=\"2076\">The chemoattractant reservoir is at the <strong data-start=\"2053\" data-end=\"2063\">bottom<\/strong> of the image<\/p>\n<\/li>\n<li data-start=\"2077\" data-end=\"2126\">\n<p data-start=\"2079\" data-end=\"2126\">Cells visibly migrate <strong data-start=\"2101\" data-end=\"2113\">downward<\/strong> in the movie<\/p>\n<\/li>\n<li data-start=\"2127\" data-end=\"2170\">\n<p data-start=\"2129\" data-end=\"2170\">The ibidi plot shows cells migrating \u201cup\u201d<\/p>\n<\/li>\n<\/ul>\n<p data-start=\"2172\" data-end=\"2189\">Nothing is wrong.<\/p>\n<p data-start=\"2191\" data-end=\"2211\">What\u2019s happening is:<\/p>\n<ul class=\"\" data-start=\"2212\" data-end=\"2393\">\n<li data-start=\"2212\" data-end=\"2291\">\n<p data-start=\"2214\" data-end=\"2291\">Image <strong data-start=\"2220\" data-end=\"2233\">+Y (down)<\/strong> is being reinterpreted as <strong data-start=\"2260\" data-end=\"2291\">+Y_ibidi (toward reservoir)<\/strong><\/p>\n<\/li>\n<li data-start=\"2292\" data-end=\"2334\">\n<p data-start=\"2294\" data-end=\"2334\">The plot is <strong data-start=\"2306\" data-end=\"2334\">not a map of the chamber<\/strong><\/p>\n<\/li>\n<li data-start=\"2335\" data-end=\"2393\">\n<p data-start=\"2337\" data-end=\"2393\">It is a <strong data-start=\"2345\" data-end=\"2393\">map of displacement relative to the gradient<\/strong><\/p>\n<\/li>\n<\/ul>\n<p class=\"\" data-start=\"2395\" data-end=\"2671\">If you intentionally tell the software the <em data-start=\"2438\" data-end=\"2445\">wrong<\/em> reservoir location, the plot rotates accordingly \u2014 often making real forward motion appear as <em data-start=\"2540\" data-end=\"2561\">perpendicular drift<\/em>. That behavior is actually a <strong data-start=\"2591\" data-end=\"2602\">feature<\/strong>, not a bug: it exposes incorrect assumptions instead of hiding them.<\/p>\n<h2 data-start=\"2678\" data-end=\"2721\">Our approach: make the gradient explicit<\/h2>\n<p data-start=\"2723\" data-end=\"2799\">To eliminate ambiguity, we designed our tool around a single explicit input:<\/p>\n<div class=\"contain-inline-size rounded-2xl corner-superellipse\/1.1 relative bg-token-sidebar-surface-primary\">\n<div class=\"sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl\/main:top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-text\">--reservoir {Bottom | Top | Left | Right}<br \/>\n<\/code><\/div>\n<\/div>\n<p data-start=\"2856\" data-end=\"2958\">This mirrors the semantics many teams already use internally (for example, in C# or MATLAB pipelines):<\/p>\n<div class=\"contain-inline-size rounded-2xl corner-superellipse\/1.1 relative bg-token-sidebar-surface-primary\">\n<div class=\"sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl\/main:top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-csharp\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-built_in\">enum<\/span> ReservoirLocation<br \/>\n{<br \/>\n    Bottom, <span class=\"hljs-comment\">\/\/ +Y_image<\/span><br \/>\n    Top,    <span class=\"hljs-comment\">\/\/ -Y_image<\/span><br \/>\n    Right,  <span class=\"hljs-comment\">\/\/ +X_image<\/span><br \/>\n    Left    <span class=\"hljs-comment\">\/\/ -X_image<\/span><br \/>\n}<br \/>\n<\/code><\/div>\n<\/div>\n<p data-start=\"3105\" data-end=\"3173\">From this, the tool constructs a <strong data-start=\"3138\" data-end=\"3172\">clean, deterministic transform<\/strong>:<\/p>\n<ul class=\"\" data-start=\"3175\" data-end=\"3360\">\n<li data-start=\"3175\" data-end=\"3228\">\n<p data-start=\"3177\" data-end=\"3228\"><strong data-start=\"3177\" data-end=\"3189\">+Y_ibidi<\/strong> is always <em data-start=\"3200\" data-end=\"3228\">toward the chemoattractant<\/em><\/p>\n<\/li>\n<li data-start=\"3229\" data-end=\"3283\">\n<p data-start=\"3231\" data-end=\"3283\"><strong data-start=\"3231\" data-end=\"3243\">+X_ibidi<\/strong> is always perpendicular (90\u00b0 clockwise)<\/p>\n<\/li>\n<li data-start=\"3284\" data-end=\"3360\">\n<p data-start=\"3286\" data-end=\"3360\">Image orientation, camera mirroring, and plot conventions no longer matter<\/p>\n<\/li>\n<\/ul>\n<p class=\"\" data-start=\"3362\" data-end=\"3450\">The result is a plot that matches <strong data-start=\"3396\" data-end=\"3424\">ibidi\u2019s conceptual model<\/strong>, not just its appearance.<\/p>\n<h2 data-start=\"3457\" data-end=\"3483\">What the free tool does<\/h2>\n<p data-start=\"3485\" data-end=\"3517\">The MetaVi Labs ibidi plot tool:<\/p>\n<ul class=\"\" data-start=\"3519\" data-end=\"3807\">\n<li data-start=\"3519\" data-end=\"3565\">\n<p data-start=\"3521\" data-end=\"3565\">Reads ibidi <strong data-start=\"3533\" data-end=\"3565\">Tracks TSV exports correctly<\/strong><\/p>\n<\/li>\n<li data-start=\"3566\" data-end=\"3615\">\n<p data-start=\"3568\" data-end=\"3615\">Normalizes tracks (per-track or center-of-mass)<\/p>\n<\/li>\n<li data-start=\"3616\" data-end=\"3666\">\n<p data-start=\"3618\" data-end=\"3666\">Transforms image coordinates \u2192 ibidi coordinates<\/p>\n<\/li>\n<li data-start=\"3667\" data-end=\"3740\">\n<p data-start=\"3669\" data-end=\"3678\">Produces:<\/p>\n<ul data-start=\"3681\" data-end=\"3740\">\n<li data-start=\"3681\" data-end=\"3699\">\n<p data-start=\"3683\" data-end=\"3699\">trajectory plots<\/p>\n<\/li>\n<li data-start=\"3702\" data-end=\"3713\">\n<p data-start=\"3704\" data-end=\"3713\">endpoints<\/p>\n<\/li>\n<li data-start=\"3716\" data-end=\"3740\">\n<p data-start=\"3718\" data-end=\"3740\">center-of-mass vectors<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li data-start=\"3741\" data-end=\"3807\">\n<p data-start=\"3743\" data-end=\"3807\">Behaves <em data-start=\"3751\" data-end=\"3767\">diagnostically<\/em> when the wrong reservoir edge is chosen<\/p>\n<\/li>\n<\/ul>\n<p data-start=\"3809\" data-end=\"3830\">It is designed to be:<\/p>\n<ul data-start=\"3831\" data-end=\"3898\">\n<li data-start=\"3831\" data-end=\"3844\">\n<p data-start=\"3833\" data-end=\"3844\">transparent<\/p>\n<\/li>\n<li data-start=\"3845\" data-end=\"3857\">\n<p data-start=\"3847\" data-end=\"3857\">debuggable<\/p>\n<\/li>\n<li data-start=\"3858\" data-end=\"3898\">\n<p data-start=\"3860\" data-end=\"3898\">and suitable for scientific validation<\/p>\n<\/li>\n<\/ul>\n<h2 data-start=\"3905\" data-end=\"3924\">Why this matters<\/h2>\n<p data-start=\"3926\" data-end=\"3937\">If you are:<\/p>\n<ul data-start=\"3938\" data-end=\"4101\">\n<li data-start=\"3938\" data-end=\"3970\">\n<p data-start=\"3940\" data-end=\"3970\">validating automated analysis,<\/p>\n<\/li>\n<li data-start=\"3971\" data-end=\"4004\">\n<p data-start=\"3973\" data-end=\"4004\">comparing multiple experiments,<\/p>\n<\/li>\n<li data-start=\"4005\" data-end=\"4053\">\n<p data-start=\"4007\" data-end=\"4053\">integrating chemotaxis into a larger pipeline,<\/p>\n<\/li>\n<li data-start=\"4054\" data-end=\"4101\">\n<p data-start=\"4056\" data-end=\"4101\">or publishing quantitative migration metrics,<\/p>\n<\/li>\n<\/ul>\n<p data-start=\"4103\" data-end=\"4164\">then <strong data-start=\"4108\" data-end=\"4163\">understanding the coordinate transform is essential<\/strong>.<\/p>\n<p data-start=\"4166\" data-end=\"4235\">Silent axis flips are dangerous.<br \/>\nExplicit transforms are trustworthy.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp; download:&nbsp; &nbsp; &nbsp;&nbsp; https:\/\/metavilabs.com\/sites\/default\/files\/share\/ibidi_plot_python.zip Chemotaxis experiments are conceptually simple \u2014 but the coordinate systems are not. If you\u2019ve ever tried to: reproduce ibidi\u2019s chemotaxis plots outside their software, validate migration direction numerically, or explain to a colleague why \u201cdown in the image\u201d becomes \u201cup in the plot\u201d, you\u2019ve probably discovered that coordinate systems are [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-153","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/posts\/153","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/comments?post=153"}],"version-history":[{"count":8,"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/posts\/153\/revisions"}],"predecessor-version":[{"id":164,"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/posts\/153\/revisions\/164"}],"wp:attachment":[{"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/media?parent=153"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/categories?post=153"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/metavilabs.com\/blog\/wp-json\/wp\/v2\/tags?post=153"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}