Last active
October 2, 2023 10:31
-
-
Save svank/6f3c2d776eea882fd271bba1bd2cc16d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Trying to understand the Python wrapper for OpenCV 3's EMD function, whose C++ documentation is [here](https://docs.opencv.org/3.4.3/d6/dc7/group__imgproc__hist.html#ga902b8e60cc7075c8947345489221e0e0). (Fun fact, OpenCV's Python bindings are [automatically generated](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_bindings/py_bindings_basics/py_bindings_basics.html), so Python documentation isn't guaranteed.)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import cv2\n", | |
"import numpy as np\n", | |
"import matplotlib.pyplot as plt" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"The EMD function expects \"signatures\" rather than images/matrices. For an N-dimensional matrix with a total of M elements, the signature is an M x (N+1) array. Each of the M rows corresponds to a single pixel/element in the original image/matrix. Within each row, the first value is the pixel/element value that occurs in the source image/matrix, and the remaining N values are the coordinates along each dimension of that pixel/element. In short, it’s a list of pixel values and their corresponding coordinates.\n", | |
"\n", | |
"We can generate this signature by iterating through the values in our source image and filling in the signature’s rows one-by-one. The order we go through the source image pixels doesn’t matter." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def img_to_sig(arr):\n", | |
" \"\"\"Convert a 2D array to a signature for cv2.EMD\"\"\"\n", | |
" \n", | |
" # cv2.EMD requires single-precision, floating-point input\n", | |
" sig = np.empty((arr.size, 3), dtype=np.float32)\n", | |
" count = 0\n", | |
" for i in range(arr.shape[0]):\n", | |
" for j in range(arr.shape[1]):\n", | |
" sig[count] = np.array([arr[i,j], i, j])\n", | |
" count += 1\n", | |
" return sig" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"[[2. 0. 0.]\n", | |
" [0. 0. 1.]\n", | |
" [0. 0. 2.]\n", | |
" [2. 1. 0.]\n", | |
" [0. 1. 1.]\n", | |
" [0. 1. 2.]\n", | |
" [0. 2. 0.]\n", | |
" [0. 2. 1.]\n", | |
" [2. 2. 2.]]\n", | |
"[[0. 0. 0.]\n", | |
" [1. 0. 1.]\n", | |
" [1. 0. 2.]\n", | |
" [2. 1. 0.]\n", | |
" [0. 1. 1.]\n", | |
" [0. 1. 2.]\n", | |
" [0. 2. 0.]\n", | |
" [2. 2. 1.]\n", | |
" [0. 2. 2.]]\n" | |
] | |
} | |
], | |
"source": [ | |
"arr1 = np.array([[2, 0, 0],\n", | |
" [2, 0, 0],\n", | |
" [0, 0, 2]])\n", | |
"\n", | |
"arr2 = np.array([[0, 1, 1],\n", | |
" [2, 0, 0],\n", | |
" [0, 2, 0]])\n", | |
"\n", | |
"sig1 = img_to_sig(arr1)\n", | |
"sig2 = img_to_sig(arr2)\n", | |
"\n", | |
"print(sig1)\n", | |
"print(sig2)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"The output of `cv2.EMD` has three values. The first is the distance between the two signatures. This appears to be normalized in some way---adding non-moving elements will reduce the distance, and doubling all pixel values doesn't affect the distance. I'm not sure what the second element---a `None`---is. The third value is the \"flow matrix\", telling you what was moved where. Per the documentation, if the input arrays (before being converted to signatures) have `size1` and `size2` elements each, the flow is a \"`𝚜𝚒𝚣𝚎𝟷×𝚜𝚒𝚣𝚎𝟸` flow matrix: `𝚏𝚕𝚘𝚠_i,j` is a flow from i-th point of signature1 to j-th point of signature2 .\"" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"0.8333333134651184\n", | |
"None\n", | |
"[[0. 1. 1. 0. 0. 0. 0. 0. 0.]\n", | |
" [0. 0. 0. 0. 0. 0. 0. 0. 0.]\n", | |
" [0. 0. 0. 0. 0. 0. 0. 0. 0.]\n", | |
" [0. 0. 0. 2. 0. 0. 0. 0. 0.]\n", | |
" [0. 0. 0. 0. 0. 0. 0. 0. 0.]\n", | |
" [0. 0. 0. 0. 0. 0. 0. 0. 0.]\n", | |
" [0. 0. 0. 0. 0. 0. 0. 0. 0.]\n", | |
" [0. 0. 0. 0. 0. 0. 0. 0. 0.]\n", | |
" [0. 0. 0. 0. 0. 0. 0. 2. 0.]]\n" | |
] | |
} | |
], | |
"source": [ | |
"dist, _, flow = cv2.EMD(sig1, sig2, cv2.DIST_L2)\n", | |
"\n", | |
"print(dist)\n", | |
"print(_)\n", | |
"print(flow)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"So that 1 in row 1, column 2 is saying that one unit of \"earth\" was moved from the coordinates in row 1 of the first signature, or (0, 0), to the coordinates in row 2 of the second signature, or (0, 1). Note that unmoved earth shows up in this flow matrix---along the diagonal in this case, since the coordinates are in the same order in the two signatures." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Now to visualize this." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def plot_flow(sig1, sig2, flow, arrow_width_scale=3):\n", | |
" \"\"\"Plots the flow computed by cv2.EMD\n", | |
" \n", | |
" The source images are retrieved from the signatures and\n", | |
" plotted in a combined image, with the first image in the\n", | |
" red channel and the second in the green. Arrows are\n", | |
" overplotted to show moved earth, with arrow thickness\n", | |
" indicating the amount of moved earth.\"\"\"\n", | |
" \n", | |
" img1 = sig_to_img(sig1)\n", | |
" img2 = sig_to_img(sig2)\n", | |
" combined = np.dstack((img1, img2, 0*img2))\n", | |
" # RGB values should be between 0 and 1\n", | |
" combined /= combined.max()\n", | |
" print('Red channel is \"before\"; green channel is \"after\"; yellow means \"unchanged\"')\n", | |
" plt.imshow(combined)\n", | |
"\n", | |
" flows = np.transpose(np.nonzero(flow))\n", | |
" for src, dest in flows:\n", | |
" # Skip the pixel value in the first element, grab the\n", | |
" # coordinates. It'll be useful later to transpose x/y.\n", | |
" start = sig1[src, 1:][::-1]\n", | |
" end = sig2[dest, 1:][::-1]\n", | |
" if np.all(start == end):\n", | |
" # Unmoved earth shows up as a \"flow\" from a pixel\n", | |
" # to that same exact pixel---don't plot mini arrows\n", | |
" # for those pixels\n", | |
" continue\n", | |
" \n", | |
" # Add a random shift to arrow positions to reduce overlap.\n", | |
" shift = np.random.random(1) * .3 - .15\n", | |
" start = start + shift\n", | |
" end = end + shift\n", | |
" \n", | |
" mag = flow[src, dest] * arrow_width_scale\n", | |
" plt.quiver(*start, *(end - start), angles='xy',\n", | |
" scale_units='xy', scale=1, color='white',\n", | |
" edgecolor='black', linewidth=mag/3,\n", | |
" width=mag, units='dots',\n", | |
" headlength=5,\n", | |
" headwidth=3,\n", | |
" headaxislength=4.5)\n", | |
" \n", | |
" plt.title(\"Earth moved from img1 to img2\")\n", | |
" \n", | |
"def sig_to_img(sig):\n", | |
" \"\"\"Convert a signature back to a 2D image\"\"\"\n", | |
" intsig = sig.astype(int)\n", | |
" img = np.empty((intsig[:, 1].max()+1, intsig[:, 2].max()+1), dtype=float)\n", | |
" for i in range(sig.shape[0]):\n", | |
" img[intsig[i, 1], intsig[i, 2]] = sig[i, 0]\n", | |
" return img" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"image/png": "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\n", | |
"text/plain": [ | |
"<matplotlib.figure.Figure at 0x7fe9b5b81c18>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
}, | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Red channel is \"before\"; green channel is \"after\"; yellow means \"unchanged\"\n" | |
] | |
}, | |
{ | |
"data": { | |
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAQ8AAAEICAYAAABBKnGGAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAGeFJREFUeJzt3XmUXGWd//H3h6wEkbBpFkIACYgKcwwRcVzIEJiBQIBx/M0PRAUBm6AgztFRjnhMwijgnBkHGR0zMMOPZTAgeyL7FmBkkU6EhEVCAkjahDUQEhKC3fn+/rg3SdGp7up+6tbSnc8r556uqvvUfb51q+pT9z51b0oRgZlZb23V6ALMrG9yeJhZEoeHmSVxeJhZEoeHmSVxeJhZEodHL0jaTVJIGtjoWqoh6QVJh3Qxb2tJcyStlHRNA2r7vqT/qne/RZN0vKQ7Gl1HLfWr8MjfFGslrS6Zfl7l8sq+yfqxLwAfBHaMiP9T784j4tyIOKUWy5b095IelLRG0twKbSdKakvtKyKujIi/Tr1/dyT9o6QnJK2S9Lykf6xFP5X06U/QLkyJiLuqWYCkgRHRXlRBfcxYYFFXj7+Pr5sVwAXAh4GDG1xLNQR8BVgAfAi4Q9LSiLiqrlVERL+ZgBeAQ7qY9yHgHuB14DXgSmB4p/t+L39C1gGzgPXAWmA18F1gNyCAE4AX8+Wc3U09lwL/AdyaL+O3wAiyF/AbwB+Aj5e03weYC7wJPAkcld9+IPASMKCk7d8CC/LLWwFnAUvyx/drYIeStl8G/pjPO7ur9QTMAN4F/pzXezJwYl73v5G9+X6U9/eDfJmvAJcD2+XL2LCOvgoszR/nVOAT+bp9E/h5N+tsOvA/KcsCBgD/mj8vzwOn5/cf2KmPU4C53dSwTf68r8/Xw2pgFDAkf+6W5dMFwJAulnEi8L8l1wP4OvAssAr4J7LX5EPAW/lzNrik/XeB5Xk/p+T337OLvi4E/r3u77dGv+ELfTDdh8eewKH5C2Bn4H7ggk73fQwYA2xdbnklL+aLga2BvyALmn266PPS/IW8PzCULLyeJ/vUGJC/Ee/N2w4CFgPfBwaTfTKuAvbO5y8BDi1Z9jXAWfnlbwEPA7vkj+8/gVn5vI/kL/7P5fN+CrR3s56mk795S94E7cAZZFuqWwMn5bXuAbwPuB64otM6mpk/5r8G3gFuBD4AjCYLnIMq9d/bZZEFy1P5etgeuIuE8MjbTATaOt12Tr6eP0D2GnoQ+Kcu7n8im4fHbOD9wEfz183d+TrcLq/7hLztYWQfFh8FhgFX0EV4kG2F/B6YWvf3W6Pf8IU+mOzNvprsE2nD9LUu2h4D/L7TfU8qs7xy4bFLyW2/A47too9LgYtLrp8BPF1yfV/gzfzyZ/MXzFYl82cB0/PLPwIuyS9vC7wNjM2vPw1MKrnfSLKth4HAD4GrSuZtQ7Z10ZvweLFTm7uBr5dc37ukvw3raHTJ/NeB/1ty/TrgW5X67+2yyML51JJ5h1BseCwBJpdc/xvghS7ufyKbh8enS67PA75Xcv1fyT/MgEuA80rm7UnX4TEDeJwutoBqOfXHMY9josyYh6QPkG3efZbszbcV2WZwqaU97OOlkstryD59u/JyyeW1Za5vuO8oYGlErC+Z/0eyT1eAXwEPSjoN+DwwPyL+mM8bC9wgqfS+HWQDn6MoeVwR8bak17upt5zO62VUXltpnQPz/jbo6ePuiV6tw5J5PX0+e6rc4x7Vi/tXehwjSvppLZlX9nFIOp1sK/azEbGuF3UUol9921LBeWTpvV9EvB/4EtkmX6nOpxjX85TjZcAYSaXPya7AnwAi4imyF+vhwBfJwmSDpcDhETG8ZBoaEX8i228es6GhpGHAjr2srfN6WEYWWKV1tvPeN0MjLCfbZdlgTFcNe6Dcc1/ucS+roo+uVHwckk4iG+eaFBHJ3wpVY0sKj23Jd2kkjQZ68vXWy2T7pPXwCNmuyHclDZI0EZgClI6g/wr4Jtn4RekxGDOBH0saCyBpZ0lH5/OuBY6U9BlJg8n226t93mcB/yBpd0nvA84Fro7Gfwvza+BMSaMlDScbAN9I0gBJQ8m2kraSNFTSoC6W9TKwo6TtSm6bBfwgX787ke0S/k/xD4NfA1+VtE8e9j8snSnpeLJ1fmhEPFeD/nukP4bHnE7HedyQ3z4DGA+sBG4mG+Sr5DyyF8ubkr5To3oBiIh3gaPItixeI/uW5isR8YeSZrPI9sXviYjXSm7/Gdlg3B2SVpEN6n0yX+6TwDfIgmc52a5atZ9Ul5AN4t1PNgD8Dtl4TqNdDNxB9k3M74FbyLaIOvL5XybbPfgl2e7r2vw+m8nX+yzgufz5H0U27tSaL38hMD+/rVARcSvZLva9ZAPTD+WzNuya/Ihs6/HRktf5zKLrqET5oItZvyPpcGBmRIyt2LiJSdoHeIJsULTRW3cb9cctD9tC5YfWT5Y0MN81nQbcUOl+zUjS30oaLGl74CfAnGYKDqgyPCTtIOlOSc/mf7fvol2HpMfyaXY1fZp1Q2S7p2+Q7bY8Tafxgj7kVOBVsq+HO4DTGlvO5qrabZH0z8CKiDhf0lnA9hHxvTLtVkdEb76aM7MmV214PANMjIjlkkaSHXizd5l2Dg+zfqba8HgzIoaXXH8jIjbbdZHUTnbodztwfkTc2MXyWoAWgG1g/w8nV9b/zRvZ6AqsX1jOaxGxc8pdKx5hKukuNh35VursXvSza0Qsk7QHcI+khRGxpHOjiLgIuAhgghStnRvYRmppdAXWL8x4zxGzvVIxPCKiy//PQtLLkkaW7La80sUyluV/n8v/H4WPkw0EmVkfVe1XtbPJTk8n/3tT5waStpc0JL+8E/BpsjMIzawPqzY8zgcOlfQs2enu5wNImlDyX8ntA7RKepzsiLnz8/M0zKwPq+qs2oh4HZhU5vZWstOeiYgHyU49N7N+xEeYmlkSh4eZJXF4mFkSh4eZJXF4mFkSh4eZJXF4mFkSh4eZJXF4mFkSh4eZJXF4mFkSh4eZJXF4mFkSh4eZJXF4mFkSh4eZJXF4bCleBR4n+yltswJU9T+JWR/yGnAjDN1mKINHDmbV2FXEXgE7k/3OmlkvOTy2FB+GbUZvwxX/fgXDhg3j2huu5YYbb+Cd9nf4855/5t0PvQtj8SvCeqyqH32qpXr+bsvcfOpLZhyUcKel8LHhH2PhvIUARAQLFy7kptk3cdX1V7Fk0RIG7TmI1buthnHANoWWbM1oBvMiYkLKXR0eZFvt06ZNq1NvjTVixAimTp1adt7LL7/MLbfcwqzrZvHA3Ae8e7MlcHhUR2SfwrbJW2+9xfTp07nwwgvp6OiA44C9Gl2VFa6K8PAebm769OmNLqEuKm153Hzzzfzqul/x2/t+y+CRg1n/V+uzXZikXzO1/sxbHsCFwIo69VWUosY8FixYsGnM49klDBo3iLd3ezsLjGGFlmzNyLstWx71dogmYJv/zr5t2Xrrrbnmhmu4ac5NvNPR6duWAbWo1pqWd1usoj/A2396m+O+dBxDRg1h1W6riGM8EGrpHB5bip2AY2DduHWsG7au0dVYP1DI4emSDpP0jKTFks4qM3+IpKvz+Y9I2q2Ifq0Xdgb+Ao9jWGGqDg9JA4BfAIcDHwGOk/SRTs1OBt6IiD2BfwN+Um2/ZtZYRWx5HAAsjojnIuJd4Crg6E5tjgYuyy9fC0yS5D1t6/vmA21Ac37vUFNFjHmMBpaWXG8DPtlVm4hol7QS2JHsdC2zPmtY6zDWr1wPg0B7ibW7r4UPAYMbXVntFbHlUW4LonMO96QNkloktUpqfbWAwsxqbc2n1rDXR/fiiXlPcN7x5/GJZZ9g8AWD2faabeFRYGWjK6ydIrY82oAxJdd3AZZ10aZN0kBgO8oclxURFwEXQXacRwG11cxcGnwyXUM7t43Ww8L5C3nxxRc588wzOfPMM1m5ciW33347V113FXf8vzvYarutWLP7Gjr27Mi2wfvJDnvVB4nlYbAImAT8iSxvvxgRT5a0+Qawb0RMlXQs8PmI+PvultvsB4ltSSfTWWXHH38848aN2+z2jo4OHnroIa6/8XquufEaXnvttebavWn0EaaSJgMXkB2feElE/FjSOUBrRMyWNBS4Avg42RbHsRHxXHfL7Avh0axH51rzuvPOOzn99NNZtGgRA8YMoOOkjsYW1OjwqIW+EB7e8rANutryaG9vf8+Wx4oVK4hxwTt7vAN70Ke3PHyEaaKfAStmzGhY/0knxlnx1oMeFAcddNDG8Fi5ciW33XYbV19/NXfcdgdbDc/HPA7ugFH0mzEPh0eibza4/xkTG1yAZRbCvuP3Zdddd+WCCy7gymuvZMH8BQzZYwirxq6Ck8i+HuiHHB5mVRj20DAWrVzEvhP23bQ78jl4d/C7jS6t5hweZlVYM2ENfJB+tTvSUw4Ps2qMb3QBjeMffTKzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJA4PM0vi8DCzJIWEh6TDJD0jabGks8rMP1HSq5Iey6dTiujXzBqn6h99kjQA+AVwKNAGPCppdkQ81anp1RFxerX9mVlzKGLL4wBgcUQ8FxHvAlcBRxewXDNrYkX83ORoYGnJ9Tbgk2Xa/Z2kzwGLgH+IiKWdG0hqAVoAdt0V+GMB1fVXW9jvolrzKWLLo9zLODpdnwPsFhH7AXcBl5VbUERcFBETImLCzjsXUJmZ1UwR4dEGjCm5vguwrLRBRLweEevyqxcD+xfQr5k1UBHh8SgwTtLukgYDxwKzSxtIGlly9Sjg6QL6NbMGqnrMIyLaJZ0O3A4MAC6JiCclnQO0RsRs4JuSjgLagRXAidX2a2aNpYjOwxPNYcIERWtro6toXvKAqRVjXkRMSLmjjzA1syQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsyQODzNL4vAwsySFhIekSyS9IumJLuZL0oWSFktaIGl8Ef2aWeMUteVxKXBYN/MPB8blUwvwy4L6NbMGKSQ8IuJ+YEU3TY4GLo/Mw8BwSSOL6NvMGqNeYx6jgaUl19vy295DUoukVkmtr75ap8rMLEm9wkNlbovNboi4KCImRMSEnXeuQ1Vmlqxe4dEGjCm5vguwrE59m1kN1Cs8ZgNfyb91ORBYGRHL69S3mdXAwCIWImkWMBHYSVIbMA0YBBARM4FbgMnAYmAN8NUi+jWzxikkPCLiuArzA/hGEX2ZWXPwEaZmlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlsThYWZJHB5mlqSQ8JB0iaRXJD3RxfyJklZKeiyfflhEv2bWOIX80DVwKfBz4PJu2jwQEUcW1J+ZNVghWx4RcT+woohlmVnfUNSWR098StLjwDLgOxHxZOcGklqAlk3X61hdXxONLqD5hV8/FVWziuoVHvOBsRGxWtJk4EZgXOdGEXERcBGAJL89zJpYXb5tiYi3ImJ1fvkWYJCknerRt5nVRl3CQ9IIKdsJkXRA3u/r9ejbzGqjkN0WSbOAicBOktqAacAggIiYCXwBOE1SO7AWODYivFti1oepWd/DHvOowGunIg+YViaYFxETUu7rI0zNLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODys71lO9huFdze4ji1cPX/0ySxNAI8Bc4DfAI/mtw8H3mhUUebwsOa0FriXTYHRVqaNf/m4oRwe1jyWAzeTBcZdwJpNs0aNGsWRRx7JvvvuyxlnnJHtcP+gIVVazuFhjdPV7khu//33Z8qUKRx55JGMHz8eSbS05D9l/EVg77pWa534d1v6qr66drrZHRk6dCiHHHIIU6ZM4YgjjmD06NHvuesLL7zAuHHjaF/fDk9RMTz8uy2VVfO7Ld7ysNrrwe7IlClTOPjggxk2bFiXizn33HNpb2+HMcCsyt1Or6ro4mwLnEw2vtufeMujr2rmtZOwO9ITI0eO5KWXXiq42Pr4F+DbjS6iDG95WONVsTvSU1deeSX3339/AcXWz9y5c7nvvvtY1ehCaiEiqprINiLvBZ4GngTOLNNGwIXAYmABML4Hyw1P3UzN8u8egqMIhr23vlGjRkVLS0vMmTMn3n777dhSTZs2LYCYBhFNOAGtkfjeL2LLox34dkTMl7QtME/SnRHxVEmbw4Fx+fRJ4Jf5X+vrTgCWbrq6ww47cN5553HyySczYMCAhpVltVf14ekRsTwi5ueXV5FtgXTeLj0auDwP44eB4ZJGVtu3NYHLyJ7dfJxzxYoVnHrqqYwZM4aWlhbmzJnDmjVruluC9VGFDphK2g24H/hYRLxVcvtvgPMj4n/z63cD34uI1m6W1cxDgo3XbGvnHTaNecxhszGPSZMmbRwkTR3zuPvuu3nggQcKKLZ+ZsyYAcA0mufbn1LVDJhWPeaxYQLeB8wDPl9m3s3AZ0qu3w3sX6ZdC9CaT40fV2jmqZn/rSf4PcE5BAdsXvv48eNj2rRp8eijj0ZHR0ePxw9GjBjR+PWeON1L48c3yk1UMeZRyJaHpEFkY+y3R8RPy8z/T2BuRMzKrz8DTIyI5d0ss/rC+rO+tHZeYtNxHnfynuM8Ro4cufE4j0mTJnV7nEdLSwsXX3xxNkR/UuVup82oruyiTMynZtTQLY+sfy4HLuimzRHArXnbA4Hf9WC5Df+0aOqpr/5bS3ALwWkEu7z3MQ0dOjSOOOKImDlzZrS1tW225fH888/HwIEDg60I/lC5r0Z/qveFiSq2PIoIj8/kT/4CskODHgMmA1OBqSUB8wtgCbAQmODwqHLqD/8Sdm++9rWvZfO/VHn5jX5j9oWpmvDwEaZ9VX9cOz3Yvdlvv/02nVVb4fwWn9tSWTW7LQ6Pvqq/r51uvr3Z6EvAFV0vwuFRmcNjS7QlrZ0AHmfToe+/y2+v8D+JOTwqc3hsibbktfMScBvZty6Tum7m8KjM4bEl8tqpyOFRWTXh4f893cySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLInDw8ySODzMLEnV4SFpjKR7JT0t6UlJZ5ZpM1HSSkmP5dMPq+3XzBprYAHLaAe+HRHzJW0LzJN0Z0Q81andAxFxZAH9mVkTqHrLIyKWR8T8/PIq4GlgdLXLNbPmVsSWx0aSdgM+DjxSZvanJD0OLAO+ExFPlrl/C9CSX10HPFFkfQXYCXit0UUAkP0Oa/PUk2mqetRk9eSaraa9U+9Y2A9dS3ofcB/w44i4vtO89wPrI2K1pMnAzyJiXIXltab+AG+tNFtNrqd7zVYPNF9N1dRTyLctkgYB1wFXdg4OgIh4KyJW55dvAQZJ2qmIvs2sMYr4tkXAfwNPR8RPu2gzIm+HpAPyfl+vtm8za5wixjw+DXwZWCjpsfy27wO7AkTETOALwGmS2oG1wLFReX/pogJqK1qz1eR6utds9UDz1ZRcT2FjHma2ZfERpmaWxOFhZkmaJjwk7SDpTknP5n+376JdR8lh7rNrUMdhkp6RtFjSWWXmD5F0dT7/kfzYlprqQU0nSnq1ZL2cUsNaLpH0iqSyx+Aoc2Fe6wJJ42tVSy9qqtvpET08XaOu66hmp5BERFNMwD8DZ+WXzwJ+0kW71TWsYQCwBNgDGAw8DnykU5uvAzPzy8cCV9d4vfSkphOBn9fpefocMB54oov5k4FbyQ5jOxB4pAlqmgj8pk7rZyQwPr+8LbCozPNV13XUw5p6vY6aZssDOBq4LL98GXBMA2o4AFgcEc9FxLvAVXldpUrrvBaYtOFr6AbWVDcRcT+wopsmRwOXR+ZhYLikkQ2uqW6iZ6dr1HUd9bCmXmum8PhgRCyH7MECH+ii3VBJrZIellR0wIwGlpZcb2PzlbyxTUS0AyuBHQuuo7c1Afxdvgl8raQxNaynkp7WW2+fkvS4pFslfbQeHXZzukbD1lFPTiHp6Toq9NyWSiTdBYwoM+vsXixm14hYJmkP4B5JCyNiSTEVUm4LovN32T1pU6Se9DcHmBUR6yRNJdsyOriGNXWn3uunJ+YDY2PT6RE3At2eHlGt/HSN64BvRcRbnWeXuUvN11GFmnq9juq65RERh0TEx8pMNwEvb9h0y/++0sUyluV/nwPmkqVoUdqA0k/tXchO5CvbRtJAYDtqu8lcsaaIeD0i1uVXLwb2r2E9lfRkHdZV1Pn0iEqna9CAdVSLU0iaabdlNnBCfvkE4KbODSRtL2lIfnknsqNbO/+/IdV4FBgnaXdJg8kGRDt/o1Na5xeAeyIfcaqRijV12l8+imyftlFmA1/Jv1E4EFi5YXe0Uep5ekTeT7ena1DnddSTmpLWUT1GoHs4IrwjcDfwbP53h/z2CcB/5Zf/ElhI9o3DQuDkGtQxmWw0eglwdn7bOcBR+eWhwDXAYuB3wB51WDeVajoPeDJfL/cCH65hLbOA5cCfyT5BTwamAlPz+QJ+kde6EJhQh/VTqabTS9bPw8Bf1rCWz5DtgiwAHsunyY1cRz2sqdfryIenm1mSZtptMbM+xOFhZkkcHmaWxOFhZkkcHmaWxOFhZkkcHmaW5P8D1L1P6R7PnTIAAAAASUVORK5CYII=\n", | |
"text/plain": [ | |
"<matplotlib.figure.Figure at 0x7fe9b5a505c0>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"fig, (ax1, ax2) = plt.subplots(1, 2)\n", | |
"ax1.imshow(arr1, cmap='gray')\n", | |
"ax1.set_title(\"Starting Distribution\")\n", | |
"ax2.imshow(arr2, cmap='gray')\n", | |
"ax2.set_title(\"Ending Distribution\")\n", | |
"plt.show()\n", | |
"\n", | |
"plot_flow(sig1, sig2, flow)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"So we see for these two distributions, 2 units of earth are distributed from the upper-left corner to the other top-row cells, another two units move along the bottom row, and a final two units in the middle row are unmoved." | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.6.4" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment