Switch to python3 and scipy from slycot

Turns out we need python3 matplotlib to make scipy work well enough to
place the poles correctly for our systems.  Rather than do it piecemeal,
do it all at once.

This includes a python opencv upgrade too to support the new python, and
a matplotlib upgrade.

Change-Id: Ic7517b5ebbfdca9cc90ae6a61d86b474f2f21b29
diff --git a/.bazelrc b/.bazelrc
index fce2616..3b5ad1f 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,6 +1,9 @@
 # Use platforms to build our code.
 build --incompatible_enable_cc_toolchain_resolution
 
+# Default to py3 since that's all we support
+build --python_version=PY3
+
 # For now we only support building on x86 Linux so we can hard-code the host
 # platform.
 build --host_platform=//tools/platforms:linux_x86
diff --git a/WORKSPACE b/WORKSPACE
index 857b0a0..210a7f0 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -146,8 +146,8 @@
 http_archive(
     name = "python_repo",
     build_file = "@//debian:python.BUILD",
-    sha256 = "cc48d18417f015238b7efb083f4ff64a80f0baf347fbaf94d5437c14f7a3d84c",
-    url = "https://www.frc971.org/Build-Dependencies/python-4.tar.gz",
+    sha256 = "c2e293cd8bab436c2bd03648d2a0853ff3e2d954644698473fcd263bb9bab037",
+    url = "https://www.frc971.org/Build-Dependencies/python-5.tar.gz",
 )
 
 http_archive(
@@ -426,8 +426,8 @@
 http_archive(
     name = "matplotlib_repo",
     build_file = "@//debian:matplotlib.BUILD",
-    sha256 = "24f8b75754e465299ddf92bd895ab111d54945a45b0f410d7cfa16b15b162e2f",
-    url = "https://www.frc971.org/Build-Dependencies/matplotlib-4.tar.gz",
+    sha256 = "a3db08d5951c1fc73f2203e3ab1f9ff4a647fff7b384c1b87f89adec61a0d77f",
+    url = "https://www.frc971.org/Build-Dependencies/matplotlib-5.tar.gz",
 )
 
 http_archive(
@@ -768,13 +768,13 @@
 )
 
 # Downloaded from:
-# https://files.pythonhosted.org/packages/05/23/7f9a896da9e7ce4170377a7a14bb804b460761f9dd66734e6ad8f001a76c/opencv_contrib_python_nonfree-4.1.1.1-cp35-cp35m-manylinux1_x86_64.whl
+# https://files.pythonhosted.org/packages/0f/13/192104516c4a3d92dc6b5e106ffcfbf0fe35f3c4faa49650205ff652af72/opencv_python-4.5.1.48-cp37-cp37m-manylinux2014_x86_64.whl
 http_archive(
     name = "opencv_contrib_nonfree_amd64",
     build_file = "@//debian:opencv_python.BUILD",
-    sha256 = "c10e7712ee1f19bf382c64fc29b5d24fa0b5bfd901eab69cef83604713e6a89e",
+    sha256 = "6d8434a45e8f75c4da5fd0068ce001f4f8e35771cc851d746d4721eeaf517e25",
     type = "zip",
-    url = "https://www.frc971.org/Build-Dependencies/opencv_contrib_python_nonfree-4.1.1.1-cp35-cp35m-manylinux1_x86_64.whl",
+    url = "https://www.frc971.org/Build-Dependencies/opencv_python-4.5.1.48-cp37-cp37m-manylinux2014_x86_64.whl",
 )
 
 http_archive(
diff --git a/aos/util/trapezoid_profile.py b/aos/util/trapezoid_profile.py
index f956bf0..af85339 100644
--- a/aos/util/trapezoid_profile.py
+++ b/aos/util/trapezoid_profile.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import numpy
 
diff --git a/aos/vision/download/downloader.py b/aos/vision/download/downloader.py
index be8fda2..3dafb48 100644
--- a/aos/vision/download/downloader.py
+++ b/aos/vision/download/downloader.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 # This file is run by shell scripts generated by the aos_vision_downloader Skylark
 # macro. Everything before the first -- is a hard-coded list of files to
diff --git a/build_tests/python_opencv.py b/build_tests/python_opencv.py
index c353c79..c4d8591 100644
--- a/build_tests/python_opencv.py
+++ b/build_tests/python_opencv.py
@@ -3,4 +3,4 @@
 import cv2
 
 if __name__ == '__main__':
-  cv2.xfeatures2d.SIFT_create()
+  cv2.SIFT_create()
diff --git a/debian/matplotlib.bzl b/debian/matplotlib.bzl
index e647682..d94efd9 100644
--- a/debian/matplotlib.bzl
+++ b/debian/matplotlib.bzl
@@ -1,102 +1,52 @@
 files = {
-    "adwaita-icon-theme_3.22.0-1+deb9u1_all.deb": "9f956279c715b657f590370751ee91e74376fe06b18cf35a05930b13bb1737a6",
-    "blt_2.5.3+dfsg-3_amd64.deb": "69377b5d18f14caf2aeb24cba3bf12b4cbac18c7c715f60b39200aa7d025561c",
-    "coreutils_8.26-3_amd64.deb": "ef6c0ab3d52a7d3e85ba4a9c04a1931264d34bab842da6e1428c8c4bda28a800",
-    "fontconfig-config_2.11.0-6.7_all.deb": "8226e3a525fef6aee3268929d15ed8352a7edfa263b8f70e3eb1a0962e772e27",
-    "fontconfig_2.11.0-6.7+b1_amd64.deb": "6976c5e1b690c9c92f55a1c53f57094fdb86e619481d2e903561436cc2235b73",
+    "blt_2.5.3+dfsg-4_amd64.deb": "624e1c02bc1e72fa77523f514a606b7b7da3399def37995d016955b8d950584b",
+    "coreutils_8.30-3_amd64.deb": "ae6e5cd6e9aaf74d66edded3931a7a6c916625b8b890379189c75574f6856bf4",
+    "fontconfig-config_2.13.1-2_all.deb": "9f5d34ba20eb156ef62d8126866a376be985c6a83fdcfb33f12cd83acac480c2",
     "fonts-dejavu-core_2.37-1_all.deb": "58d21a255606191e6512cca51f32c4480e7a798945cc980623377696acfa3cfc",
-    "fonts-liberation_1.07.4-2_all.deb": "b342d0382aaf8d64a61c347b6e83f84c1ad50aa4ed3df661ece9010fce3ee72a",
-    "fonts-lyx_2.2.2-1_all.deb": "55f9e6aa61acb6d0e25dec9c71c1da21af83eea942c6f64d0646db7b96ff5048",
-    "gnome-icon-theme_3.12.0-2_all.deb": "6f918206118943badc16751bc682ce764223652bf5d70b7683b680235a585788",
-    "gtk-update-icon-cache_3.22.11-1_amd64.deb": "efb1b77e02a201a7e5bc3474bb2dbedb707de0bd734482a7f8d5df4579c5913a",
-    "hicolor-icon-theme_0.15-1_all.deb": "ccad1133347b30513230c896e0072f58910affbbc8947e084d482bef6c5eea00",
-    "libatk1.0-0_2.22.0-1_amd64.deb": "dd714b9581b5f6dcb4fe711285117b6a63fa03bd40ad7adad28838883f60236d",
-    "libatk1.0-data_2.22.0-1_all.deb": "e48623184d071483e69d1c02dd0163f9a439b9e11c46628ebd230851b2625b1c",
-    "libaudit-common_2.6.7-2_all.deb": "42c502f6d37d8d7b77e659d23c58b7e13401b52003782276eb891dfb1bf958c2",
-    "libaudit1_2.6.7-2_amd64.deb": "9359b088d9679e6464bddcbc2074a0733eac05cf30857f18ef6c0b65aeaa6591",
-    "libblkid1_2.29.2-1+deb9u1_amd64.deb": "796fa1b6cb42e836d84c3a4e1e54fcc31b5a45f98c0d215345d17030d7bb3dcd",
-    "libbsd0_0.8.3-1_amd64.deb": "030e441cc6368041536a69adcaed6b4f4a37e0145817fc3eff16f37e3a81ecae",
-    "libcairo2_1.14.8-1_amd64.deb": "1f6ebf5f89c05c2e4aae96343f41446e2a847a3686c1dd22db39922df1e60f73",
-    "libcap-ng0_0.7.7-3+b1_amd64.deb": "9a428663ad84b2b886b57e22d45bb39e924eb1cfe1e137fd73e2e65cabb3347d",
-    "libcroco3_0.6.11-3_amd64.deb": "3a1e1af6a81c04035d67c1928460270448ac5ae30b79f68cd1c2acabb77debf8",
-    "libdatrie1_0.2.10-4+b1_amd64.deb": "07349230986b454db6cec4f20fd648235e89f5a7ba258371e5da1dd7ba3992c9",
-    "libfontconfig1_2.11.0-6.7+b1_amd64.deb": "8e4ac54fe770ff84c4b596e5e66dacc94efad08940fa354a8c112db06c67d588",
-    "libfreetype6_2.6.3-3.2_amd64.deb": "4fd6eb9c7f5d06a6cc2a7c35966aa8c06289f0cb599e0b291be235c63f28db1a",
-    "libgdk-pixbuf2.0-0_2.36.5-2+deb9u2_amd64.deb": "0dca760e915f5ec6ef2445135d9daf50d9a7246ec9ef6e1386dceab6a2445028",
-    "libgdk-pixbuf2.0-common_2.36.5-2+deb9u2_all.deb": "3895bb256529fbb72d9428681af732deee023b3210700857c2febd63022b0921",
-    "libglib2.0-0_2.50.3-2+deb9u1_amd64.deb": "06da7996a1ce90653f2ea6fb09cd44f5a90b27cc5b61db97aa7bf959848fc4c1",
-    "libgraphite2-3_1.3.10-1_amd64.deb": "abea07610dab52ea704b01231c179ea02fcf6ecb7606e0775fb3150916c8276b",
-    "libgtk2.0-0_2.24.31-2_amd64.deb": "2406ad832e6f677de8107d2b2590cf7e4fa03d90bd644810cab76d54c7ced248",
-    "libgtk2.0-common_2.24.31-2_all.deb": "cf9c7c0dd4fc876fe2088c0d23e082e4a8e44c424184bc277577b753f7ca8ced",
-    "libharfbuzz0b_1.4.2-1_amd64.deb": "7d66151e6f07835f707a073a08e449e1ce971885af71ad6c474e0e85a2439610",
-    "libicu57_57.1-6+deb9u3_amd64.deb": "631db26c25104e61028693b7014ef0ceb1d1f7e22796c8e5bc1110d406bdd060",
-    "libjbig0_2.1-3.1+b2_amd64.deb": "9646d69eefce505407bf0437ea12fb7c2d47a3fd4434720ba46b642b6dcfd80f",
-    "libjpeg62-turbo_1.5.1-2_amd64.deb": "55b4208bca9e772cd3d6e6a3f6bf3949d170e6da77e53b0ba59abb8f1658bb64",
-    "libjs-jquery-ui_1.12.1+dfsg-4_all.deb": "65efba16f621f9b1fafefca0ecafac59df690776924c1137c4352fef18000713",
-    "libjs-jquery_3.1.1-2+deb9u1_all.deb": "672c6d54028eb6aae02f85a99a753d5d3bb8c1a573040b981d2b853f1f27c238",
-    "libmount1_2.29.2-1+deb9u1_amd64.deb": "37d9c4f275392ac8bf94dbc2aaeade4d95fd7605e1fbb1075ac77663599b6e6b",
-    "libpam-modules-bin_1.1.8-3.6_amd64.deb": "2f6279ebec1e48d18ab0f33a321cb99c09f3eaf1a36273ff486060db871d3741",
-    "libpam-modules_1.1.8-3.6_amd64.deb": "173bdb5b8dc7ab9bca3d09cca7456000678ccce0d08463ed8298a723eb26f031",
-    "libpam0g_1.1.8-3.6_amd64.deb": "20f72da20526de9e94226d6f645772664fbc0e35f1c4a130048720322db3d3d2",
-    "libpango-1.0-0_1.40.5-1_amd64.deb": "2973a15ad26aa1051dce9fa4c0ee7e06e4b03e99cf74b6e8697cb7384b346e8d",
-    "libpangocairo-1.0-0_1.40.5-1_amd64.deb": "c7bb62778c4e6c8086028e50656c3eff6e238c1467da6f3020c3d843668060d9",
-    "libpangoft2-1.0-0_1.40.5-1_amd64.deb": "d668b89229869a68cc411a25feff5016ff76c5ed0ae23855196f2a46f8f88e75",
-    "libpixman-1-0_0.34.0-1_amd64.deb": "6c4155c4e217481aa728d39d2ba7d6ca6c88bb1e2b342ca24b2714e61903a3e1",
-    "libpng16-16_1.6.28-1+deb9u1_amd64.deb": "1d0b8014cba18f84ce77398da9db9c71e1a8ce53fd8157cd7bf9ccf1d56a42f4",
-    "librsvg2-2_2.40.16-1+b1_amd64.deb": "90397a051434d2a6484aeb7e7dd2b6610e54b156a94d615e917d514ac4bf196e",
-    "librsvg2-common_2.40.16-1+b1_amd64.deb": "5604d832893fc49b5ebd66255e92fd7e79dbc6d3891475d00a127e389bf2f575",
-    "libsemanage-common_2.6-2_all.deb": "f9ccd6a271af92cc2b557d5a3e2725b0d3a44a336704cded5f77e1a7a8503840",
-    "libsemanage1_2.6-2_amd64.deb": "17fca1a09c432bcd025b00207785c00244594c7cce1e878a2729d50c82bbfc6a",
-    "libsepol1_2.6-2_amd64.deb": "cfb1248894d4817acd435e68ff9b142e170c67829de1dacb6cea667d1b1fdfab",
-    "libtcl8.6_8.6.6+dfsg-1+b1_amd64.deb": "1f44458197319632c84a10fbea3f969958106ffc44e6a9a54f9ed71725b8c7a7",
-    "libthai-data_0.1.26-1_all.deb": "9e1709138f7d324fbcb6b62d48f6a2b365630e8f81c42db12e04d03d0e71f9f6",
-    "libthai0_0.1.26-1_amd64.deb": "e9cfd37dc2b1e067ee70cd17c153235c852b83754cb039e86e554c3376853f3b",
-    "libtiff5_4.0.8-2+deb9u4_amd64.deb": "16d5ca9b2c846ed56b141b7cff251abe17183566f55a1f5ef6c9a26ba8ff4bde",
-    "libtk8.6_8.6.6-1+b1_amd64.deb": "5ad20e1c4d036dcb90b2e93ce89073a5e65198be0b69a972a33106af2c2b6f6f",
-    "libustr-1.0-1_1.0.4-6_amd64.deb": "9cdfba388ccb3d9767325075d0291bc2f92f90e505c98e01a93012c796f68580",
-    "libuuid1_2.29.2-1+deb9u1_amd64.deb": "6e16f3033f0147714af101e189dd6b260a986417c6bad8bb1cd9ffe70fa107c8",
-    "libx11-6_1.6.4-3+deb9u1_amd64.deb": "bfb881d47a72a6d79a66327bf43e106c13c4dfbd9e87987ff551c3c0cd6bc92e",
-    "libx11-data_1.6.4-3+deb9u1_all.deb": "c589d2decc374dff78da717a4716c0d79d9646abb7ccc4a64e1f61534a2ba3b3",
-    "libxau6_1.0.8-1_amd64.deb": "b03b2d0d400c2002a2d38300bd6630306abb0ff325c3d4a4447ecceb58335228",
-    "libxcb-render0_1.12-1_amd64.deb": "127ebbad060f4ba88c174b980c27cb4458e9782c65349d80034c3feb012c7343",
-    "libxcb-shm0_1.12-1_amd64.deb": "3bed0565cfd144bc4f1752985572c3d62b4dd193fb1f1c4cb5f05f82878b459b",
-    "libxcb1_1.12-1_amd64.deb": "358ac6d450042d1792e3b2093ed73530774e6bd7600536a2acc327b83b201384",
-    "libxcomposite1_0.4.4-2_amd64.deb": "043c878356954f4521c401b160d554809115c472ca384d9f793c1c7542316eb9",
-    "libxcursor1_1.1.14-1+deb9u2_amd64.deb": "af4908f3f2bcfe78586823eaf8ed65d838936cb26698c520538717367d836dc6",
-    "libxdamage1_1.1.4-2+b3_amd64.deb": "860d474e576074711a58e248feb9fb62086f641cbfa986145cc6c105ef750cc5",
+    "fonts-liberation_1.07.4-9_all.deb": "c936aebbfd0af7851399ae5ab08bb01744f5e3381f7678fb87cc77114f95ef53",
+    "fonts-lyx_2.3.2-1_all.deb": "9b66e257d16c593fc78ebb14f5b72c4a8e00f09984bc2242359a0b16b4efbd87",
+    "libbsd0_0.9.1-2_amd64.deb": "0827321e85d36200759e3ec621fc05154c752534c330ffc5472ad75bbb8eb913",
+    "libfontconfig1_2.13.1-2_amd64.deb": "6766d0bcfc615fb15542efb5235d38237ccaec4c219beb84dbd22d1662ccea8f",
+    "libfreetype6_2.9.1-3+deb10u2_amd64.deb": "93f009440fd1ffcc4b3afdbc413eccc1d8101145a262ca0d0c305fc7029f2417",
+    "libjs-jquery-ui_1.12.1+dfsg-5_all.deb": "42918c7650c60346ac2c5c7596af220bfc858ff2c7d5f63eb02be8902aaa5755",
+    "libjs-jquery_3.3.1~dfsg-3_all.deb": "1276015fa73712e78262995343c5f5313a5c2efe7eff91741c2b9de1fe721fdf",
+    "libpng16-16_1.6.36-6_amd64.deb": "82a252478465521cde9d5af473df01ed79f16e912effc5971892a574e9113500",
+    "libtcl8.6_8.6.9+dfsg-2_amd64.deb": "7b5d095b83e13b9b571cfecde55834b770735e29ff23a52d45e9f4692d4c64a1",
+    "libtk8.6_8.6.9-2_amd64.deb": "a250aba06a5fc9c90622b6e1c3560ff351f945ed7234f61267ec3688370d1770",
+    "libx11-6_1.6.7-1+deb10u1_amd64.deb": "f9d62eaa734828d4282fe4c17613c1a688af4cccbe2819eb691b3aaf615e882f",
+    "libx11-data_1.6.7-1+deb10u1_all.deb": "02f795889390fa0e1f29c6ecdd4a30cd0aae39c0c6b1379410055404b0897c66",
+    "libxau6_1.0.8-1+b2_amd64.deb": "a7857b726c3e0d16cda2fbb9020d42e024a3160d54ef858f58578612276683e8",
+    "libxcb1_1.13.1-2_amd64.deb": "87d9ed9340dc3cb6d7ce024d2e046a659d91356863083715d2c428a32e908833",
     "libxdmcp6_1.1.2-3_amd64.deb": "ecb8536f5fb34543b55bb9dc5f5b14c9dbb4150a7bddb3f2287b7cab6e9d25ef",
     "libxext6_1.3.3-1+b2_amd64.deb": "724901105792e983bd0e7c2b46960cd925dd6a2b33b5ee999b4e80aaf624b082",
-    "libxfixes3_5.0.3-1_amd64.deb": "3b307490c669accd52dc627ad4dc269a03632ca512fbc7b185b572f76608ff4e",
-    "libxft2_2.3.2-1+b2_amd64.deb": "25e6bdcfdccdb332b2d415b98170c1fe4841156397dfb04368ac6a9631401670",
-    "libxi6_1.7.9-1_amd64.deb": "fe26733adf2025f184bf904caf088a5d3f6aa29a8863b616af9cafaad85b1237",
-    "libxinerama1_1.1.3-1+b3_amd64.deb": "56977ee53b18388cc8735dc7a64e709c08c70104344b4f11f255470f08e58c00",
-    "libxml2_2.9.4+dfsg1-2.2+deb9u2_amd64.deb": "287fdcf90302893234c4eecc357002a464f7dff43f77adb86dfaee6d32ae1c4d",
-    "libxrandr2_1.5.1-1_amd64.deb": "8fdd8ba4a8ad819731d6bbd903b52851a2ec2f9ef4139d880e9be421ea61338c",
+    "libxft2_2.3.2-2_amd64.deb": "cd71384b4d511cba69bcee29af326943c7ca12450765f44c40d246608c779aad",
     "libxrender1_0.9.10-1_amd64.deb": "3ea17d07b5aa89012130e2acd92f0fc0ea67314e2f5eab6e33930ef688f48294",
-    "libxss1_1.2.2-1_amd64.deb": "0f2fc4eff464b63a4fafed9ab2e499e1804dcee85c5d9a89e53c3ed6a2a06b88",
-    "passwd_4.4-4.1_amd64.deb": "81448b1a55cfe82ca3f2791bea75fc4a41cd82eacfee3247c37a04deb9fc4a1a",
+    "libxss1_1.2.3-1_amd64.deb": "85cce16368f08a878fa892fbc54520fc654d00769cde6d300b8b802734a993c0",
+    "node-jquery_2.2.4+dfsg-4_all.deb": "92dcf4950fb9a8ee2f50557af2ca50ca426f5fb89fc6812009ff2775e02e330e",
+    "python-backports.functools-lru-cache_1.5-3_all.deb": "e9c96b612156453ce58a47ae906e0e52f3b2a78a505f12fd860419822e205d0a",
     "python-cycler_0.10.0-1_all.deb": "ed00546b732c9361205ca392ef178995ca50e647e2940261c61229968be3de76",
-    "python-dateutil_2.5.3-2_all.deb": "3f440c3fea55c5a33b92ce4f30033997294e2e289c58712d2c4d38f169985315",
-    "python-functools32_3.2.3.2-3_all.deb": "f0c35fb01be2f3c0c60ba1074ffb0b12de63dd0a7dcb4c7b09a44714d6447e1f",
-    "python-matplotlib-data_2.0.0+dfsg1-2_all.deb": "ca349445130c54967e27c9a74e19c4678099183b946432428e627fcad577390b",
-    "python-matplotlib_2.0.0+dfsg1-2_amd64.deb": "7ffcef500f7774e403b38e02d88811ff81a4800b54d23515e429d1eecbd76616",
-    "python-pyparsing_2.1.10+dfsg1-1_all.deb": "0bc6785731658b613e51cd19c5ab54ac467063c2c99e4c56f2f459084e665f33",
-    "python-six_1.10.0-3_all.deb": "547c1f63a8cf07d99a7a79da562a5a938bfaa08b292c1fa479afdebafbb955fa",
-    "python-subprocess32_3.2.7-2_amd64.deb": "cc3581f29ad93cfbdeb06088d6e3b1feb16212e592b18d621dd910e2cdd87232",
-    "python-tk_2.7.13-1_amd64.deb": "bf2dd505e155ae2de493ae86aa6217169149be5ebd1878ed9fc2ffcea2b91975",
-    "python-tz_2016.7-0.3_all.deb": "de87fbcd276903afb2f94c6e76554e50fc339a379c3e3a0c8ef22c0f446cb39b",
+    "python-dateutil_2.7.3-3_all.deb": "eb051f2d84622c46551428b5b4f7045ca3c188a31afc3934511f83f59080bde4",
+    "python-kiwisolver_1.0.1-2+b1_amd64.deb": "fe5e6de843c3840e0786973167d2103e11d39c4ee2dac532baf1efdf1a9f4c3c",
+    "python-matplotlib-data_3.0.2-2_all.deb": "da12bc151e86ec180fc86fc27edf7213e7b0f532e1d10e77a410ac9dcc92ef0c",
+    "python-matplotlib2-data_2.2.3-6_all.deb": "9680fb9e627dfc81c82d1019adc1b53306185bd6996c456c6fc990027980967b",
+    "python-matplotlib_2.2.3-6_amd64.deb": "b48c890f2e19369bb8eb20d5fe19f1fec12101110350128ffb8cb684533382f1",
+    "python-pyparsing_2.2.0+dfsg1-2_all.deb": "4f92606287eaebaf61a63c3e483d96f3a07d88132cf2b7774300e07089ca969a",
+    "python-six_1.12.0-1_all.deb": "e2fab198138d00ca05a2c79aa5490acf87cf22e2496f45721c3b8837d32e3f3b",
+    "python-subprocess32_3.5.3-1_amd64.deb": "d29658e8a52621f44b77bc31623c045ae7f8f159ba57539c62fe02b7f6e7581e",
+    "python-tk_2.7.16-2_amd64.deb": "97ec414f9328e29e70720e0ac4fbc4483299c3b98c01746491bd5740506b131b",
+    "python-tz_2019.1-1_all.deb": "354996dc154d9cbc15f1d7e85b930bd66f6082795cf57b7cbc8e65c54a20575e",
     "python3-cycler_0.10.0-1_all.deb": "b49d81a972054f2df915002cd8ba9225fb6bd07a633487dda5813c137f6cfb76",
-    "python3-dateutil_2.5.3-2_all.deb": "398d719e19dc7cc581e009c35d583860f427720bf971665a7739c29cd19d3470",
-    "python3-matplotlib_2.0.0+dfsg1-2_amd64.deb": "8f5d3509d4f5451468c6de44fc8dfe391c3df4120079adc01ab5f13ff4194f5a",
-    "python3-pyparsing_2.1.10+dfsg1-1_all.deb": "ee8d7f04f841248127e81b3d356d37e623ed29da284b28c7d2b8a5b34f0eebba",
-    "python3-six_1.10.0-3_all.deb": "597005e64cf70e4be97170a47c33287f70a1c87a2979d47a434c10c9201af3ca",
-    "python3-tk_3.5.3-1_amd64.deb": "67489a1c86a9e501dbe2989cd72b5b2c70511fe3829af3567a009271b61fdbb5",
-    "python3-tz_2016.7-0.3_all.deb": "5f1c7db456aac5fe9b0ea66d7413c12660c7652ae382c640f71c517a05d39551",
-    "shared-mime-info_1.8-1+deb9u1_amd64.deb": "d6591f13ee1200c4f0b5581c2299eb7b8097a6b04742dc333e34a7bb7ba47532",
-    "tk8.6-blt2.5_2.5.3+dfsg-3_amd64.deb": "88587a928e2bd692650d98c1483b67f1dee1fed57730077c895e689462af1569",
+    "python3-dateutil_2.7.3-3_all.deb": "f35233cee90828b9b167a8d6db121be6b78607ff280cc2c0503d37fcaa8c4751",
+    "python3-kiwisolver_1.0.1-2+b1_amd64.deb": "ea86280396a3665fc4355da9f2b0f43198fdda31cd4b8e66ed478b305fef3f29",
+    "python3-matplotlib_3.0.2-2_amd64.deb": "d4cd5d0227ac8013141fc9340b7a37a740e584b9517fd73d9a288f7135257f2b",
+    "python3-pyparsing_2.2.0+dfsg1-2_all.deb": "cfc257030609c96acfd5589d751c33cfd50e0870f22b3485fc75240c7c7ad19e",
+    "python3-six_1.12.0-1_all.deb": "ec43cea7798b07e39ad53bb4088f6db17ef1fb01abaebab0641da0ba0e6819e4",
+    "python3-tk_3.7.3-1_amd64.deb": "e869ac21e43dcea7b09fa23848e285122d4b4255a0d738d7eb2bebd6d92fbe2a",
+    "sensible-utils_0.0.12_all.deb": "2043859f8bf39a20d075bf52206549f90dcabd66665bb9d6837273494fc6a598",
+    "tk8.6-blt2.5_2.5.3+dfsg-4_amd64.deb": "752ed35d41bc98a1b79c61b196cfd479a695b6c8d6e6756a18221c4ece501f95",
     "ttf-bitstream-vera_1.10-8_all.deb": "328def7f581bf94b3b06d21e641f3e5df9a9b2e84e93b4206bc952fe8e80f38a",
-    "tzdata_2019c-0+deb9u1_all.deb": "80c9809dafc62ec741cbf3024130253de6047af31a10f0c86bb17f2d12ad10d5",
-    "ucf_3.0036_all.deb": "796a65e765d6045007175531d512c720f4eb04e7f3326b79b848bc6123947225",
+    "tzdata_2021a-0+deb10u1_all.deb": "00da63f221b9afa6bc766742807e398cf183565faba339649bafa3f93375fbcb",
+    "ucf_3.0038+nmu1_all.deb": "d02a82455faab988a52121f37d97c528a4f967ed75e9398e1d8db571398c12f9",
 }
 
 def build_matplotlib(version, tkinter_py_version = None, copy_shared_files = True):
diff --git a/debian/matplotlib_init.patch b/debian/matplotlib_init.patch
index 8d67dc6..4cc155a 100644
--- a/debian/matplotlib_init.patch
+++ b/debian/matplotlib_init.patch
@@ -1,13 +1,5 @@
 --- a/__init__.py	2018-07-11 15:57:58.086509489 -0700
 +++ b/__init__.py	2018-07-11 16:04:15.004795500 -0700
-@@ -102,6 +102,7 @@
- from __future__ import (absolute_import, division, print_function,
-                         unicode_literals)
- 
-+import os
- import six
- import sys
- import distutils.version
 @@ -110,6 +111,31 @@
  __version__ = str('1.4.2')
  __version__numpy__ = str('1.6')  # minimum required numpy version
@@ -33,7 +25,7 @@
 +                                         'tcltk', 'tcl8.6')
 +
 +# Tell fontconfig where to find matplotlib's sandboxed font files.
-+os.environ["FONTCONFIG_PATH"] = os.path.join(_matplotlib_base, "etc/fonts/")
++os.environ["FONTCONFIG_PATH"] = os.path.join(_matplotlib_base, "etc/fonts")
 +os.environ["FONTCONFIG_FILE"] = os.path.join(_matplotlib_base, "etc/fonts/fonts.conf")
 +os.environ["FONTCONFIG_SYSROOT"] = _matplotlib_base
 +
diff --git a/debian/python.BUILD b/debian/python.BUILD
index c840a19..17543df 100644
--- a/debian/python.BUILD
+++ b/debian/python.BUILD
@@ -1,21 +1,21 @@
 package(default_visibility = ["@//debian:__pkg__"])
 
 cc_library(
-    name = "python3.5_lib",
+    name = "python3.7_lib",
     srcs = [
-        "usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0",
+        "usr/lib/x86_64-linux-gnu/libpython3.7m.so.1.0",
     ],
     hdrs = glob(["usr/include/**/*.h"]),
     includes = [
         "usr/include/",
-        "usr/include/python3.5m/",
+        "usr/include/python3.7m/",
     ],
     target_compatible_with = ["@platforms//cpu:x86_64"],
     visibility = ["//visibility:public"],
 )
 
 cc_library(
-    name = "python3.5_f2py",
+    name = "python3.7_f2py",
     srcs = [
         "usr/lib/python3/dist-packages/numpy/f2py/src/fortranobject.c",
     ],
@@ -31,44 +31,7 @@
     ],
     visibility = ["//visibility:public"],
     deps = [
-        ":python3.5_lib",
-    ],
-)
-
-cc_library(
-    name = "python2.7_lib",
-    srcs = [
-        "usr/lib/x86_64-linux-gnu/libpython2.7.so",
-        "usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0",
-    ],
-    hdrs = glob([
-        "usr/include/**/*.h",
-    ]),
-    includes = [
-        "usr/include/",
-        "usr/include/python2.7/",
-    ],
-    target_compatible_with = ["@platforms//cpu:x86_64"],
-    visibility = ["//visibility:public"],
-)
-
-cc_library(
-    name = "python2.7_f2py",
-    srcs = [
-        "usr/lib/python2.7/dist-packages/numpy/f2py/src/fortranobject.c",
-    ],
-    hdrs = [
-        "usr/lib/python2.7/dist-packages/numpy/f2py/src/fortranobject.h",
-    ],
-    copts = [
-        "-Wno-error",
-    ],
-    includes = [
-        "usr/lib/python2.7/dist-packages/numpy/f2py/src/",
-    ],
-    visibility = ["//visibility:public"],
-    deps = [
-        ":python2.7_lib",
+        ":python3.7_lib",
     ],
 )
 
@@ -97,8 +60,6 @@
     srcs = glob([
         "usr/lib/python3/dist-packages/numpy",
         "usr/lib/python3/dist-packages/scipy",
-        "usr/lib/python2.7/dist-packages/numpy",
-        "usr/lib/python2.7/dist-packages/scipy",
     ]),
     visibility = ["//visibility:public"],
 )
diff --git a/debian/python.bzl b/debian/python.bzl
index 0b6bf7b..7ef0e9d 100644
--- a/debian/python.bzl
+++ b/debian/python.bzl
@@ -1,51 +1,60 @@
 files = {
-    "dh-python_2.20170125_all.deb": "653083af906f433f5baf4f26ee1d1320e2251a98f20994eacc4797d34bbabaaa",
-    "libblas-common_3.7.0-2_amd64.deb": "38b76abd8d7f317c86c2b890e9643db13d4880c6e3745e67c1469b2e465dff5d",
-    "libblas3_3.7.0-2_amd64.deb": "a6873de8c875e4f9ce390cd1c2e877e83388a2fabc0a3a22bf9978f86f6bde23",
-    "libdb5.3_5.3.28-12+deb9u1_amd64.deb": "ade55bfbd803f3dff0c4c122ac497d734fecac5a9142d8e8b7f86275e7aa91c0",
-    "libexpat1-dev_2.2.0-2+deb9u3_amd64.deb": "ba5134fcc4eec7c7c3e65292b9db4d51f237a0f4559616827d239061b6998266",
-    "libexpat1_2.2.0-2+deb9u3_amd64.deb": "12857a50ede17ec9957b7cdb04a80dbb603df567501af3aef8ee53932a015cb8",
-    "libffi6_3.2.1-6_amd64.deb": "a385cd7ce2cc6c73e271c4692d4c152d96d6c9ad756c3a36bf503f9c2a462de4",
-    "libgfortran3_6.3.0-18+deb9u1_amd64.deb": "30b77b353a633725ec7e7491868ceb653afd0e1f4bdb2a0206db493d6d40db38",
-    "liblapack3_3.7.0-2_amd64.deb": "5355e377bc6db7eb83cc50d9270a3d9803fae579f4c98f4396f4e74bbd4cb9f3",
-    "libmpdec2_2.4.2-1_amd64.deb": "59f5a992082294cf206217512484bcf7e6575d47e277c977bbd3a652aa799fed",
-    "libncursesw5_6.0+20161126-1+deb9u2_amd64.deb": "6e232f52ce3d76d0406c1968bdb4277da81475e015ffca404fcb3f858b863f6c",
-    "libpython-dev_2.7.13-2_amd64.deb": "483c172e68ec447846c2bfac2dc91981ab5af152024cd7dfb234cb2aac351aee",
-    "libpython-stdlib_2.7.13-2_amd64.deb": "ae3936394d8b343a9c020f277e93b29ae848fe695c86aaf16f76aa5af2bb0781",
-    "libpython2.7-dev_2.7.13-2+deb9u3_amd64.deb": "c4bf4fe25c6c20166122bce8f68e8d9f010261a8a7bed2fba666bd159650c6c5",
-    "libpython2.7-minimal_2.7.13-2+deb9u3_amd64.deb": "101ef840f9bc7b9168494ca54e94da6d30359be6a921b0085a8709e6e9026334",
-    "libpython2.7-stdlib_2.7.13-2+deb9u3_amd64.deb": "9d776465833a2640441e14627c1a43118f5d461922731d2260e295cbd14694e5",
-    "libpython2.7_2.7.13-2+deb9u3_amd64.deb": "9c0c3f3aef753ba4c81f7635c386e394a6d560af0b07270f0dc83fb4334ec4e0",
-    "libpython3-dev_3.5.3-1_amd64.deb": "912b4323027ba90ede5f9db3eb6a5d123c8dab6738d716dd8bec10cd9c8a1690",
-    "libpython3-stdlib_3.5.3-1_amd64.deb": "800f1de01b9b3e831cd01c4400fa21f50596d77d8fb5d494a04cdda437181d61",
-    "libpython3.5-dev_3.5.3-1+deb9u1_amd64.deb": "683aea6b0b9266dbde902256985667715488e9724a3bcc3741e386b6fdc5d977",
-    "libpython3.5-minimal_3.5.3-1+deb9u1_amd64.deb": "db962e6e460a48d1190ff644441b7e0cd622d84fe4d6e664fd309d21e8c77e81",
-    "libpython3.5-stdlib_3.5.3-1+deb9u1_amd64.deb": "c2c804139153e6e63df8f1d6dbc18a7d398880360bd978f9589bcb09471bfbb0",
-    "libpython3.5_3.5.3-1+deb9u1_amd64.deb": "5184f95e9d5434fb5e1c6d86befb9d9078ce0b71e643b312988928f5846972f4",
-    "libquadmath0_6.3.0-18+deb9u1_amd64.deb": "a98030608d1b8eb07d2028ae5b03e1a83f6ae07fda4c765c096f5042992a27f0",
-    "libreadline7_7.0-3_amd64.deb": "9d90cd02554b1f0637fc1fbceb3bd69feb576b07a4d25df82970ab04f71f35da",
-    "libsqlite3-0_3.16.2-5+deb9u1_amd64.deb": "f448b8dbab36f859cc37627472a5df6aa281ab05fdab386296ee6c9503a5f666",
-    "libssl1.1_1.1.0l-1~deb9u1_amd64.deb": "385ce03f4b995e2f756ff92f8fb2c431f51c51866e08695d329223ca6cd3bfaa",
-    "libstdc++6_6.3.0-18+deb9u1_amd64.deb": "d05373fbbb0d2c538fa176dfe71d1fa7983c58d35a7a456263ca87e8e0d45030",
-    "libtinfo5_6.0+20161126-1+deb9u2_amd64.deb": "1d249a3193568b5ef785ad8993b9ba6d6fdca0eb359204c2355532b82d25e9f5",
-    "mime-support_3.60_all.deb": "d0685a72625b474b0a8a85a9465701eab60f16d6f0359ac120ec603a5b37044f",
-    "python-decorator_4.0.11-1_all.deb": "d4f72b36358c1db27b7eb20b0727fdee6d5cdbd409f8a343dacc9badb17a3e15",
-    "python-dev_2.7.13-2_amd64.deb": "287a1407d97c0d6fca9a877eb1c26171df0067e82babf5bf9137d10365a6a32c",
-    "python-minimal_2.7.13-2_amd64.deb": "425f1e6b2e1047a208b2e7c334455b8db2d0c03ea1ca761c4f53893a244c65c9",
-    "python-numpy_1.12.1-3_amd64.deb": "76b19ba44b2cce9c79b5150ed0bad5b1247c3f810612b00a397f3a2df7227356",
-    "python-scipy_0.18.1-2_amd64.deb": "30f7231c534b121f67587a8426098f091ee572a9071b2c87106fd365619dcd10",
-    "python2.7-dev_2.7.13-2+deb9u3_amd64.deb": "88a6d17f20944800e4f6fa4581f47ade05cc6f0fb66eb7d43a50da6ef0364639",
-    "python2.7-minimal_2.7.13-2+deb9u3_amd64.deb": "f0d2a33dce0b3723aa8ce8e0f82e6287035dcf88ae3038210718c661b13981b6",
-    "python2.7_2.7.13-2+deb9u3_amd64.deb": "ef3ba6e3c867b95c49da1a30c4505ffccc11f9fef39c3448204a46bab72b6d7e",
-    "python3-decorator_4.0.11-1_all.deb": "63cca50dc848d1d68c4ac8d4b5a6f8886f592977822019673529f797938b5091",
-    "python3-dev_3.5.3-1_amd64.deb": "288b35a42eb3f88fd0065d1522edfb19db8e66cb64fad6c2abae0a0359fe584d",
-    "python3-minimal_3.5.3-1_amd64.deb": "4184ede57b6764f4adb38193711d363f83e1797e4e680cabf277304c16dd9fd0",
-    "python3-numpy_1.12.1-3_amd64.deb": "acb93bfe28f5928ec50baff5486f8a84ee675cebb3fbcaccb29492b2dcaa3424",
-    "python3-scipy_0.18.1-2_amd64.deb": "3fc84587b650884ee197fadeb72a689afa4d42e4de0b4a7296fb9a035abaae1f",
-    "python3.5-dev_3.5.3-1+deb9u1_amd64.deb": "2e73e416b39e06bd0356ebddc51751d77872c31acbb46fb25d7630ad99c2fd96",
-    "python3.5-minimal_3.5.3-1+deb9u1_amd64.deb": "e1f791c8351b8cce3bd269d5bdf9e7fd5537cbd18ca53922f2ea7c061615b99c",
-    "python3.5_3.5.3-1+deb9u1_amd64.deb": "4cd0fbbc04529e055c7b403ff31c327955312c8b5f9227a3634facd1d2717541",
-    "python3_3.5.3-1_amd64.deb": "3d8a80b4c0d35d33ae067b3fab230f6f13bc5ae28597e5942a90ba91b85e2a86",
-    "python_2.7.13-2_amd64.deb": "4d5dc639f4fb86b924ca7728188982e9062a7f056f64c4f95b48d6a3a0ec042b",
-    "readline-common_7.0-3_all.deb": "d03bb49ebe933b06515323f876300d7e71dff572f73a29656dd06e846844e54d",
+    "dh-python_3.20190308_all.deb": "e649e5f2652123695e3579e6ee7f46d42126ea04bd1d0b3f7c10b8f79c39f188",
+    "libblas3_3.8.0-2_amd64.deb": "7161d85be1e755bb605b2a3f65d7c556c5851ed0379b723b3f9d54a5eada5fd5",
+    "libdb5.3_5.3.28+dfsg1-0.5_amd64.deb": "c7f0e9a423840731362ee52d4344c0bcf84318fbc06dad4fefe0e61d9e7062bc",
+    "libexpat1-dev_2.2.6-2+deb10u1_amd64.deb": "0295d8395e706c2c08c7b8ce2244585fef7116b4853898ab680affdbf39693cf",
+    "libexpat1_2.2.6-2+deb10u1_amd64.deb": "d60dee1f402ee0fba6d44df584512ae9ede73e866048e8476de55d9b78fa2da1",
+    "libffi6_3.2.1-9_amd64.deb": "d4d748d897e8e53aa239ead23a18724a1a30085cc6ca41a8c31b3b1e1b3452f4",
+    "libgfortran5_8.3.0-6_amd64.deb": "c76cb39bb3da74c5315e0d9577adc45bd39bf2d21fb7885e724429e5b4ed0ffe",
+    "liblapack3_3.8.0-2_amd64.deb": "29f7df1fb03bc42b38872d37f2d1fc43ac0943b117dd766d8771247363ab4419",
+    "libmpdec2_2.4.2-2_amd64.deb": "9ca85e6e2645a5e660431294320658ec7a2910d9fed90ca4e648c1211a2b844b",
+    "libncursesw6_6.1+20181013-2+deb10u2_amd64.deb": "7dffe9602586300292960f2e3cf4301acfc64a91aed6fa41ea2e719ae75788b3",
+    "libpython-dev_2.7.16-1_amd64.deb": "f52ccc0a85e67a1cc17aff5fcefc82b27f63a47ead938912e3154889c765eb10",
+    "libpython-stdlib_2.7.16-1_amd64.deb": "97cae1e38c030291c6b3ecb0a17ee6c6536d19912bf1d53a55172d85935420ae",
+    "libpython2-dev_2.7.16-1_amd64.deb": "67ef742bbef38acafe6a64a6b46cc6ad32575568a3788ad2b57726c38eac0cd1",
+    "libpython2-stdlib_2.7.16-1_amd64.deb": "82f7754428246edfe11626417a20fef3506eed454cf0e3c2bb9867af5e57e310",
+    "libpython2.7-dev_2.7.16-2+deb10u1_amd64.deb": "8f7146f8f4308999c052e82e2012b82a8cd475593d3195ef91ab5e68236044ef",
+    "libpython2.7-minimal_2.7.16-2+deb10u1_amd64.deb": "8a54dfa6c30ced68dafc159d88adb8c096697a993023bb5e31f2dfd93e386474",
+    "libpython2.7-stdlib_2.7.16-2+deb10u1_amd64.deb": "96c9e7ad71da07f47b7356b416b7f5d6d9e8eda1404b2c8a8ba8edda3799177b",
+    "libpython2.7_2.7.16-2+deb10u1_amd64.deb": "e5dcd5ff5be854e9c7645f1a349701e809078051ef88dd119dc55d07c2e1f7bb",
+    "libpython3-dev_3.7.3-1_amd64.deb": "5fdcd3ce63c9daf001455db58a99efbe6b7be75755729a9370d59deaecf92ac9",
+    "libpython3-stdlib_3.7.3-1_amd64.deb": "4f8883d378e698aa89b7bd4b68ce8e7cca01c961d3df87fafe4c079bb4668f5b",
+    "libpython3.7-dev_3.7.3-2+deb10u2_amd64.deb": "309facf814abf1e9a04ee4830fe2e6941c82f3d7d95714556d78f1ad58e8576c",
+    "libpython3.7-minimal_3.7.3-2+deb10u2_amd64.deb": "783822ae63e3bbdadeabfc8121fc920652248acf9de6be9b1bb42d2a4a0baaf1",
+    "libpython3.7-stdlib_3.7.3-2+deb10u2_amd64.deb": "bfa1a449fcd229456f73b6fb6af24aeb9ad3d6b998b650b7e5615b8101e5d3c0",
+    "libpython3.7_3.7.3-2+deb10u2_amd64.deb": "94c34bee59dfd4f21a249d0a8efb999dbeff5d2b63a172b10b7548c22be93073",
+    "libquadmath0_8.3.0-6_amd64.deb": "766684a231a740b434468e1c7146353fcddff7b8e14644a82672299459c53c34",
+    "libreadline7_7.0-5_amd64.deb": "01e99d68427722e64c603d45f00063c303b02afb53d85c8d1476deca70db64c6",
+    "libsqlite3-0_3.27.2-3+deb10u1_amd64.deb": "19268b796e62f754400c67c69cb759220089cf10aaa5dfd72a84ab1a818caa08",
+    "libssl1.1_1.1.1d-0+deb10u4_amd64.deb": "b02b468f0fad929b5d2b38ae05607c22c4f1ef70adc2688fb02b9d9514d6ac51",
+    "libstdc++6_8.3.0-6_amd64.deb": "5cc70625329655ff9382580971d4616db8aa39af958b7c995ee84598f142a4ee",
+    "libtinfo6_6.1+20181013-2+deb10u2_amd64.deb": "7f39c7a7b02c3373a427aa276830a6e1e0c4cc003371f34e2e50e9992aa70e1a",
+    "libuuid1_2.33.1-0.1_amd64.deb": "90b90bef4593d4f347fb1e74a63c5609daa86d4c5003b14e85f58628d6c118b2",
+    "mime-support_3.62_all.deb": "776efd686af26fa26325450280e3305463b1faef75d82b383bb00da61893d8ca",
+    "python-decorator_4.3.0-1.1_all.deb": "b974cce9a7c6a67e3ff58d0c863e39767b1335540e55042046011f6cb570a768",
+    "python-dev_2.7.16-1_amd64.deb": "47c0a6843c479f4c94b9cfcf25872ca19997ff3c9bf880da1263e99759439c5a",
+    "python-minimal_2.7.16-1_amd64.deb": "25d63ab661c3210f09b269cd8f72f56cd142b370cca5ab48f373fd9816d2f6dc",
+    "python-numpy_1.16.2-1_amd64.deb": "475d88a1df8f5884baa8c2cb20287081ce0043825d09d25bdf84e69531cf7c92",
+    "python-pkg-resources_40.8.0-1_all.deb": "4d6c21f3420a60a689949fc094c2615a3d79d527dd4b25c6886f52323a864061",
+    "python-scipy_1.1.0-7_amd64.deb": "c19fae9daf4a86ae6b4227b13b9dd0c1f99d835706700e815e364028773ac9c3",
+    "python2-dev_2.7.16-1_amd64.deb": "1fc06a982ba81bdde60e74a203d48c2e730e077ae6f8ba46937d365d2ad72a32",
+    "python2-minimal_2.7.16-1_amd64.deb": "538d88329dd3c6d9936fae3051e4f4f0e2a4300f0ba5252afcdd8787871f2caf",
+    "python2.7-dev_2.7.16-2+deb10u1_amd64.deb": "fa4905e28309d85d9ea7e5bb23bf6a8ab2f75bd4af004ec0193e4017f2d32c18",
+    "python2.7-minimal_2.7.16-2+deb10u1_amd64.deb": "639a24fc4130b31ff9406db4fdc248cf6ce311e53136ccdfb10fa1134dd5faf4",
+    "python2.7_2.7.16-2+deb10u1_amd64.deb": "66951c2dbc143d93b6b8757eb749bbd380f340c454301a27bd45ff712b9cffb0",
+    "python2_2.7.16-1_amd64.deb": "d412efde85b1bbdec65f7a4f4f3fc9754eb33b4712218d51eaaa8f6b950613e0",
+    "python3-decorator_4.3.0-1.1_all.deb": "28c6f928e02e500c29b3f79554acc4c3d840160ba2279003f11f8dc397e54faf",
+    "python3-dev_3.7.3-1_amd64.deb": "ae68b1c4f498fbeda0457611d2ccbd7d9bee00ad77b277314134cb262fc128a4",
+    "python3-distutils_3.7.3-1_all.deb": "6918af11061d3141990e78f5ad0530ec0f9a188cac27113d9de2896203efc13f",
+    "python3-lib2to3_3.7.3-1_all.deb": "227e2a2d12922c00dee9e55d8c5b889cfc5e72a54b85c2a509fa1664c2e9e137",
+    "python3-minimal_3.7.3-1_amd64.deb": "9c937923b35ac24f5cb6be81626f00dd6b810fc0889e5b3b08b7ffc9d179ff1b",
+    "python3-numpy_1.16.2-1_amd64.deb": "d5c458459022c3755718acc11e1ddfaf0590dad1c87e4fbb984705f402db2c22",
+    "python3-pkg-resources_40.8.0-1_all.deb": "43783cd63c996b36fcf29bfd8be8c44666148c9129ade88985876d7f9c0bf2f6",
+    "python3-scipy_1.1.0-7_amd64.deb": "684be03532d336181ae93c78ac013eccaba4cc81b3d4beaae3da0acf4dde2d84",
+    "python3.7-dev_3.7.3-2+deb10u2_amd64.deb": "6483aa767345bf0f1f62a9d63b9065a05bab69af9bdc69dd2abb1f6e22dbb79c",
+    "python3.7-minimal_3.7.3-2+deb10u2_amd64.deb": "c3f234591469e8fc3eaac9cb6a20732b10e4392d29cf8c743216c6ea841a2133",
+    "python3.7_3.7.3-2+deb10u2_amd64.deb": "82cc1d080c6921b84ff4177889f6dfc4315dd8c2f9ce31dda5fa083629d48c32",
+    "python3_3.7.3-1_amd64.deb": "eb7862c7ad2cf5b86f3851c7103f72f8fa45b48514ddcf371a8e4ba8f02a79e5",
+    "python_2.7.16-1_amd64.deb": "10b7416134f75b6b49afae4fc6d07c5e061509bedb1ec413747407b234564a0d",
+    "readline-common_7.0-5_all.deb": "153d8a5ddb04044d10f877a8955d944612ec9035f4c73eec99d85a92c3816712",
 }
diff --git a/debian/slycot.BUILD b/debian/slycot.BUILD
index 9ef3055..82cc72c 100644
--- a/debian/slycot.BUILD
+++ b/debian/slycot.BUILD
@@ -4,134 +4,6 @@
 load("@//tools/build_rules:fortran.bzl", "f2c_library")
 load("@//tools/build_rules:select.bzl", "compiler_select")
 
-# We can't create _wrapper.so in the slycot folder, and can't move it.
-# The best way I found to do this is to modify _wrapper.pyf to instead generate
-# a _fortranwrapper.so library, and then place a _wrapper.py file in slycot/
-# which loads _fortranwrapper from the correct location.  This means that I
-# don't need to modify the repository.
-genrule(
-    name = "_fortranwrapper_pyf",
-    srcs = ["slycot/src/_wrapper.pyf"],
-    outs = ["slycot/src/_fortranwrapper.pyf"],
-    cmd = "cat $(SRCS) | sed 's/_wrapper/_fortranwrapper/' > $(OUTS)",
-    target_compatible_with = ["@platforms//cpu:x86_64"],
-)
-
-# The contents of the file telling f2py how to translate various types. The
-# format doesn't seem to be very well-documented, but this seems to make all the
-# argument types match up.
-_f2py_f2cmap_contents = """{
-"integer": {
-  "check m>=0": "long",
-  "check n>=0": "long",
-  "check p>=0": "long",
-  "": "long",
-},
-"logical": {
-  "": "long",
-},
-}"""
-
-# Now generate the module wrapper.
-genrule(
-    name = "_fortranwrappermodule",
-    srcs = [
-        "slycot/src/analysis.pyf",
-        "slycot/src/synthesis.pyf",
-        "slycot/src/_fortranwrapper.pyf",
-        "slycot/src/math.pyf",
-        "slycot/src/transform.pyf",
-    ],
-    outs = ["_fortranwrappermodule.c"],
-    cmd = "\n".join([
-        "cat > .f2py_f2cmap <<END",
-        _f2py_f2cmap_contents,
-        "END",
-        "readlink -f .f2py_f2cmap",
-        " ".join([
-            "$(location @python_repo//:f2py)",
-            "$(location :slycot/src/_fortranwrapper.pyf)",
-            "--include-paths external/slycot_repo/slycot/src/",
-            "--coutput $(OUTS)",
-        ]),
-        " ".join([
-            "sed",
-            "\"s/Generation date.*/Generation date: redacted/\"",
-            "-i $(OUTS)",
-        ]),
-    ]),
-    target_compatible_with = ["@platforms//cpu:x86_64"],
-    tools = [
-        "@python_repo//:f2py",
-    ],
-)
-
-# Build it.
-cc_library(
-    name = "slycot_c",
-    srcs = [
-        ":_fortranwrappermodule",
-    ],
-    copts = [
-        "-Wno-error",
-        "-Wno-incompatible-pointer-types-discards-qualifiers",
-        "-Wno-cast-align",
-        "-Wno-unused-parameter",
-        "-Wno-missing-field-initializers",
-        "-Wno-unused-function",
-        "-Wno-unused-but-set-variable",
-    ],
-    target_compatible_with = ["@platforms//cpu:x86_64"],
-    deps = [
-        ":slicot",
-        "@python_repo//:python2.7_f2py",
-        "@python_repo//:python2.7_lib",
-    ],
-    # Make sure this gets included in the .so.
-    alwayslink = True,
-)
-
-# Link it all together.  Make sure all the deps get static linked into a single
-# shared object, which will then be loaded by the Python interpreter.
-cc_binary(
-    name = "slycot/_fortranwrapper.so",
-    linkshared = True,
-    linkstatic = True,
-    target_compatible_with = ["@platforms//cpu:x86_64"],
-    deps = [
-        ":slicot",
-        ":slycot_c",
-    ],
-)
-
-# Generate the _wrapper file which loads _fortranwrapper and pretends.
-genrule(
-    name = "_wrapper",
-    outs = ["slycot/_wrapper.py"],
-    cmd = "echo \"from slycot._fortranwrapper import *\" > $(OUTS)",
-    output_to_bindir = True,
-)
-
-# Now present a python library for slycot
-py_library(
-    name = "slycot",
-    srcs = [
-        "slycot/__init__.py",
-        "slycot/analysis.py",
-        "slycot/examples.py",
-        "slycot/math.py",
-        "slycot/synthesis.py",
-        "slycot/transform.py",
-        ":_wrapper",
-    ],
-    data = [
-        ":slycot/_fortranwrapper.so",
-    ],
-    imports = ["."],
-    target_compatible_with = ["@platforms//cpu:x86_64"],
-    visibility = ["//visibility:public"],
-)
-
 f2c_library(
     name = "slicot",
     srcs = glob(["slycot/src/*.f"]),
diff --git a/frc971/analysis/BUILD b/frc971/analysis/BUILD
index 45e947b..2901463 100644
--- a/frc971/analysis/BUILD
+++ b/frc971/analysis/BUILD
@@ -6,22 +6,6 @@
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library", "flatbuffer_ts_library")
 load("//aos:config.bzl", "aos_config")
 
-py_binary(
-    name = "plot_action",
-    srcs = [
-        "logentry.py",
-        "logreader.py",
-        "plot_action.py",
-        "plotter.py",
-    ],
-    legacy_create_init = False,
-    target_compatible_with = ["@platforms//os:linux"],
-    deps = [
-        ":python_init",
-        "@matplotlib_repo//:matplotlib2.7",
-    ],
-)
-
 py_library(
     name = "python_init",
     srcs = ["__init__.py"],
@@ -41,7 +25,7 @@
         "//aos/events:simulated_event_loop",
         "//aos/events/logging:logger",
         "@com_github_google_glog//:glog",
-        "@python_repo//:python3.5_lib",
+        "@python_repo//:python3.7_lib",
     ],
 )
 
diff --git a/frc971/analysis/logentry.py b/frc971/analysis/logentry.py
deleted file mode 100644
index 08aa469..0000000
--- a/frc971/analysis/logentry.py
+++ /dev/null
@@ -1,275 +0,0 @@
-#!/usr/bin/python
-
-import re
-
-"""
-A regular expression to match the envelope part of the log entry.
-Parsing of the JSON msg is handled elsewhere.
-"""
-LOG_RE = re.compile("""
-  (.*?)              # 1 name
-  \((\d+)\)          # 2 pid
-  \((\d+)\)          # 3 message_index
-  :\s
-  (\w+?)             # 4 level
-  \s+at\s+
-  (\d+\.\d+)s        # 5 time
-  :\s
-  ([A-Za-z0-9_./-]+) # 6 filename
-  :\s
-  (\d+)              # 7 linenumber
-  :\s
-  (.*)               # 8 msg
-  """, re.VERBOSE)
-
-class LogEntry:
-  """
-  This class provides a way to parse log entries.
-  The header portion of the log entry is parsed eagerly.
-  The structured portion of a log entry is parsed on demand.
-  """
-
-  def __init__(self, line):
-    """Populates a LogEntry from a line."""
-    self.line = line
-    m = LOG_RE.match(line)
-    if m is None:
-        print("LOG_RE failed on", line)
-        return
-    self.name = m.group(1)
-    self.pid_index = int(m.group(2))
-    self.msg_index = int(m.group(3))
-    self.level = m.group(4)
-    self.time = float(m.group(5))
-    self.filename = m.group(6)
-    self.linenumber = m.group(7)
-    self.msg = m.group(8)
-    self.struct_name = None
-
-  def __str__(self):
-    """Formats the data cleanly."""
-    return '%s(%d)(%d): %s at %fs: %s: %d: %s' % (
-        self.name, self.pid, self.msg_index, self.level, self.time, self.filename, self.linenumber, self.msg)
-
-  def ParseStruct(self):
-    """Parses the message as a structure.
-
-    Returns:
-      struct_name, struct_type, json dict.
-    """
-    if self.struct_name:
-        # We've already parsed the structural part. Return the cached result
-        return (self.struct_name, self.struct_type, self.struct_json)
-
-    struct_name_index = self.msg.find(':')
-    struct_name = self.msg[0:struct_name_index]
-
-    struct_body = self.msg[struct_name_index+2:]
-    tokens = []
-    this_token = ''
-    # For the various deliminators, append what we have found so far to the
-    # list and the token.
-    for char in struct_body:
-      if char == '{':
-        if this_token:
-          tokens.append(this_token)
-          this_token = ''
-        tokens.append('{')
-      elif char == '}':
-        if this_token:
-          tokens.append(this_token)
-          this_token = ''
-        tokens.append('}')
-      elif char == '[':
-        if this_token:
-          tokens.append(this_token)
-          this_token = ''
-        tokens.append('[')
-      elif char == ']':
-        if this_token:
-          tokens.append(this_token)
-          this_token = ''
-        tokens.append(']')
-      elif char == ':':
-        if this_token:
-          tokens.append(this_token)
-          this_token = ''
-        tokens.append(':')
-      elif char == ',':
-        if this_token:
-          tokens.append(this_token)
-          this_token = ''
-        tokens.append(',')
-      elif char == ' ':
-        if this_token:
-          tokens.append(this_token)
-          this_token = ''
-      else:
-        this_token += char
-    if this_token:
-      tokens.append(this_token)
-
-    struct_type = tokens[0]
-    json = dict()
-    # Now that we have tokens, parse them.
-    self.JsonizeTokens(json, tokens, 1)
-
-    # Cache the result to avoid having to reparse.
-    self.struct_name = struct_name
-    self.struct_type = struct_type
-    self.struct_json = json
-
-    return (struct_name, struct_type, json)
-
-  def JsonizeTokens(self, json, tokens, token_index):
-    """Creates a json-like dictionary from the provided tokens.
-
-    Args:
-      json: dict, The dict to stick the elements in.
-      tokens: list of strings, The list with all the tokens in it.
-      token_index: int, Where to start in the token list.
-
-    Returns:
-      int, The last token used.
-    """
-    # Check that the message starts with a {
-    if tokens[token_index] != '{':
-      print(tokens)
-      print('Expected { at beginning, found', tokens[token_index])
-      return None
-
-    # Eat the {
-    token_index += 1
-
-    # States and state variable for parsing elements.
-    STATE_INIT = 'init'
-    STATE_HAS_NAME = 'name'
-    STATE_HAS_COLON = 'colon'
-    STATE_EXPECTING_SUBMSG = 'submsg'
-    STATE_EXPECTING_COMMA = 'comma'
-    parser_state = STATE_INIT
-
-    while token_index < len(tokens):
-      if tokens[token_index] == '}':
-        # Finish if there is a }
-        return token_index + 1
-      elif tokens[token_index] == '{':
-        if parser_state != STATE_EXPECTING_SUBMSG:
-          print(tokens)
-          print(parser_state)
-          print('Bad input, was not expecting {')
-          return None
-        # Found a submessage, parse it.
-        sub_json = dict()
-        token_index = self.JsonizeTokens(sub_json, tokens, token_index)
-        json[token_name] = sub_json
-        parser_state = STATE_EXPECTING_COMMA
-      else:
-        if parser_state == STATE_INIT:
-          # This token is the name.
-          token_name = tokens[token_index]
-          parser_state = STATE_HAS_NAME
-        elif parser_state == STATE_HAS_NAME:
-          if tokens[token_index] != ':':
-            print(tokens)
-            print(parser_state)
-            print('Bad input, found', tokens[token_index], 'expected :')
-            return None
-          # After a name, comes a :
-          parser_state = STATE_HAS_COLON
-        elif parser_state == STATE_HAS_COLON:
-          # After the colon, figure out what is next.
-          if tokens[token_index] == '[':
-            # Found a sub-array!
-            sub_array = []
-            token_index = self.__JsonizeTokenArray(sub_array, tokens, token_index)
-            json[token_name] = sub_array
-            parser_state = STATE_EXPECTING_COMMA
-          elif tokens[token_index + 1] == '{':
-            # Found a sub-message, trigger parsing it.
-            parser_state = STATE_EXPECTING_SUBMSG
-          else:
-            # This is just an element, move on.
-            json[token_name] = tokens[token_index]
-            parser_state = STATE_EXPECTING_COMMA
-        elif parser_state == STATE_EXPECTING_COMMA:
-          # Complain if there isn't a comma here.
-          if tokens[token_index] != ',':
-            print(tokens)
-            print(parser_state)
-            print('Bad input, found', tokens[token_index], 'expected ,')
-            return None
-          parser_state = STATE_INIT
-        else:
-          print('Bad parser state')
-          return None
-        token_index += 1
-
-    print('Unexpected end')
-    return None
-
-  def __JsonizeTokenArray(self, sub_array, tokens, token_index):
-    """Parses an array from the provided tokens.
-
-    Args:
-      sub_array: list, The list to stick the elements in.
-      tokens: list of strings, The list with all the tokens in it.
-      token_index: int, Where to start in the token list.
-
-    Returns:
-      int, The last token used.
-    """
-    # Make sure the data starts with a '['
-    if tokens[token_index] != '[':
-      print(tokens)
-      print('Expected [ at beginning, found', tokens[token_index + 1])
-      return None
-
-    # Eat the '['
-    token_index += 1
-
-    # Loop through the tokens.
-    while token_index < len(tokens):
-      if tokens[token_index + 1] == ',':
-        # Next item is a comma, so we should just add the element.
-        sub_array.append(tokens[token_index])
-        token_index += 2
-      elif tokens[token_index + 1] == ']':
-        # Next item is a ']', so we should just add the element and finish.
-        sub_array.append(tokens[token_index])
-        token_index += 1
-        return token_index
-      else:
-        # Otherwise, it must be a sub-message.
-        sub_json = dict()
-        token_index = self.JsonizeTokens(sub_json, tokens, token_index + 1)
-        sub_array.append(sub_json)
-        if tokens[token_index] == ',':
-          # Handle there either being another data element.
-          token_index += 1
-        elif tokens[token_index] == ']':
-          # Handle the end of the array.
-          return token_index
-        else:
-          print('Unexpected ', tokens[token_index])
-          return None
-
-    print('Unexpected end')
-    return None
-
-
-if __name__ == '__main__':
-  def ParseLine(line):
-    return LogEntry(line)
-
-  print('motor_writer(2240)(07421): DEBUG   at 0000000819.99620s: ../../frc971/output/motor_writer.cc: 105: sending: .aos.controls.OutputCheck{pwm_value:221, pulse_length:2.233333}')
-  line = ParseLine('motor_writer(2240)(07421): DEBUG   at 0000000819.99620s: ../../frc971/output/motor_writer.cc: 105: sending: .aos.controls.OutputCheck{pwm_value:221, pulse_length:2.233333}')
-  if '.aos.controls.OutputCheck' in line.msg:
-    print(line)
-    print(line.ParseStruct())
-
-  line = ParseLine('claw(2263)(19404): DEBUG   at 0000000820.00000s: ../../aos/controls/control_loop-tmpl.h: 104: position: .frc971.control_loops.ClawGroup.Position{top:.frc971.control_loops.HalfClawPosition{position:1.672153, front:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:52}, calibration:.frc971.HallEffectStruct{current:f, posedge_count:6, negedge_count:13}, back:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:62}, posedge_value:0.642681, negedge_value:0.922207}, bottom:.frc971.control_loops.HalfClawPosition{position:1.353539, front:.frc971.HallEffectStruct{current:f, posedge_count:2, negedge_count:150}, calibration:.frc971.HallEffectStruct{current:f, posedge_count:8, negedge_count:18}, back:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:6}, posedge_value:0.434514, negedge_value:0.759491}}')
-  print(line.ParseStruct())
-
-  line = ParseLine('joystick_proxy(2255)(39560): DEBUG   at 0000000820.00730s: ../../aos/prime/input/joystick_input.cc: 61: sending: .aos.RobotState{joysticks:[.aos.Joystick{buttons:0, axis:[0.000000, 1.000000, 1.000000, 0.000000]}, .aos.Joystick{buttons:0, axis:[-0.401575, 1.000000, -1.007874, 0.000000]}, .aos.Joystick{buttons:0, axis:[0.007874, 0.000000, 1.000000, -1.007874]}, .aos.Joystick{buttons:0, axis:[0.000000, 0.000000, 0.000000, 0.000000]}], test_mode:f, fms_attached:f, enabled:T, autonomous:f, team_id:971, fake:f}')
-  print(line.ParseStruct())
diff --git a/frc971/analysis/logreader.py b/frc971/analysis/logreader.py
deleted file mode 100644
index 7449569..0000000
--- a/frc971/analysis/logreader.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/python
-
-import collections
-from frc971.analysis.logentry import LogEntry
-
-class Dataset(object):
-  def __init__(self):
-    self.time = []
-    self.data = []
-
-  def Add(self, time, data):
-    self.time.append(time)
-    self.data.append(data)
-
-class CollectingLogReader(object):
-  """
-  Reads log files and collected requested data.
-  """
-  def __init__(self):
-    self.signal = collections.OrderedDict()
-
-  def Add(self, binary, struct_instance_name, *data_search_path):
-    """
-    Specifies a specific piece of data to collect
-
-    Args:
-      binary: str, The name of the executable that generated the log.
-      struct_instance_name: str, The name of the struct instance whose data
-                            contents should be collected.
-      data_search_path: [str], The path into the struct of the exact piece of
-                        data to collect.
-
-    Returns:
-      None
-    """
-    self.signal[(binary, struct_instance_name, data_search_path)] = Dataset()
-
-  def HandleFile(self, f):
-    """
-    Parses the specified log file.
-
-    Args:
-      f: str, The filename of the log whose data to parse.
-
-    Returns:
-      None
-    """
-    with open(f, 'r') as fd:
-      for line in fd:
-        try:
-            self.HandleLine(line)
-        except Exception as ex:
-            # It's common for the last line of the file to be malformed.
-            print("Ignoring malformed log entry: ", line, ex)
-
-  def HandleLine(self, line):
-    """
-    Parses a line from a log file and adds the data to the plot data.
-
-    Args:
-      line: str, The line from the log file to parse
-
-    Returns:
-      None
-    """
-    pline = LogEntry(line)
-
-    for key in self.signal:
-      value = self.signal[key]
-      binary = key[0]
-      struct_instance_name = key[1]
-      data_search_path = key[2]
-      boolean_multiplier = False
-      multiplier = 1.0
-
-      # If the plot definition line ends with a "-b X" where X is a number then
-      # that number gets drawn when the value is True. Zero gets drawn when the
-      # value is False.
-      if len(data_search_path) >= 2 and data_search_path[-2] == '-b':
-        multiplier = float(data_search_path[-1])
-        boolean_multiplier = True
-        data_search_path = data_search_path[:-2]
-
-      if len(data_search_path) >= 2 and data_search_path[-2] == '-m':
-        multiplier = float(data_search_path[-1])
-        data_search_path = data_search_path[:-2]
-
-      # Make sure that we're looking at the right binary structure instance.
-      if binary == pline.name:
-        if pline.msg.startswith(struct_instance_name + ': '):
-          # Traverse the structure as specified in `data_search_path`.
-          # This lets the user access very deeply nested structures.
-          _, _, data = pline.ParseStruct()
-          for path in data_search_path:
-            data = data[path]
-
-          if boolean_multiplier:
-            if data == 'T':
-              value.Add(pline.time, multiplier)
-            else:
-              value.Add(pline.time, 0)
-          else:
-            value.Add(pline.time, float(data) * multiplier)
diff --git a/frc971/analysis/plot_action.py b/frc971/analysis/plot_action.py
deleted file mode 100755
index d157065..0000000
--- a/frc971/analysis/plot_action.py
+++ /dev/null
@@ -1,97 +0,0 @@
-#!/usr/bin/python
-
-import sys
-import numpy
-from frc971.analysis.plotter import Plotter
-import argparse
-
-def ReadPlotDefinitions(filename):
-  """
-  Read a file with plotting definitions.
-
-  A plotting definition is a single line that defines what data to search for
-  in order to plot it. The following in a file would duplicate the default
-  behaviour:
-
-    fridge goal height
-    fridge goal angle
-    fridge goal velocity
-    fridge goal angular_velocity
-    fridge output left_arm
-    fridge output right_arm
-    fridge output left_elevator
-    fridge output right_elevator
-
-  Lines are ignored if they start with a hash mark (i.e. '#').
-
-  Lines that end with a "-b X" where X is a number then it designates that line
-  as plotting a boolean value. X is the value plotted when the boolean is true.
-  When the boolean is false then the values is plotted as zero. For example,
-  the following boolean value is drawn to toggle between 2.0 and 0 when the
-  boolean is True and False, respectively:
-
-    fridge status zeroed -b 2.0
-
-  Args:
-    filename: The name of the file to read the definitions from.
-
-  Returns:
-    [[str]]: The definitions in the specified file.
-  """
-  defs = []
-  with open(filename) as fd:
-    for line in fd:
-      raw_defs = line.split()
-
-      # Only add to the list of definitions if the line's not empty and it
-      # doesn't start with a hash.
-      if raw_defs and not raw_defs[0].startswith('#'):
-        defs.append(raw_defs)
-
-  return defs
-
-
-def maybeint(x):
-  try:
-    return int(x)
-  except ValueError:
-    return x
-
-
-def main():
-  # Parse all command line arguments.
-  arg_parser = argparse.ArgumentParser(description='Log Plotter')
-  arg_parser.add_argument('log_file', metavar='LOG_FILE', type=str, \
-      help='The file from which to read logs and plot.')
-  arg_parser.add_argument('--plot-defs', '-p', action='store', type=str, \
-      help='Read the items to plot from this file.')
-  arg_parser.add_argument('--no-binary', '-n', action='store_true', \
-      help='Don\'t print the binary name in the legend.')
-
-  args = arg_parser.parse_args(sys.argv[1:])
-
-  p = Plotter()
-
-  # If the user defines the list of data to plot in a file, read it from there.
-  if args.plot_defs:
-    defs = ReadPlotDefinitions(args.plot_defs)
-    for definition in defs:
-      mapped_definitions = map(maybeint, definition[2:])
-      p.Add(definition[0], definition[1], *mapped_definitions)
-
-  # Otherwise use a pre-defined set of data to plot.
-  else:
-    p.Add('fridge', 'goal', 'height')
-    p.Add('fridge', 'goal', 'angle')
-    p.Add('fridge', 'goal', 'velocity')
-    p.Add('fridge', 'goal', 'angular_velocity')
-
-    p.Add('fridge', 'output', 'left_arm')
-    p.Add('fridge', 'output', 'right_arm')
-    p.Add('fridge', 'output', 'left_elevator')
-    p.Add('fridge', 'output', 'right_elevator')
-
-  p.PlotFile(args.log_file, args.no_binary)
-
-if __name__ == '__main__':
-  main()
diff --git a/frc971/analysis/plotter.py b/frc971/analysis/plotter.py
deleted file mode 100755
index 6d23587..0000000
--- a/frc971/analysis/plotter.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/python
-
-from frc971.analysis.logreader import CollectingLogReader
-import matplotlib
-from matplotlib import pylab
-from matplotlib.font_manager import FontProperties
-
-class Plotter(CollectingLogReader):
-  """
-  A CollectingLogReader that plots collected data.
-  """
-
-  def PlotFile(self, f, no_binary_in_legend=False):
-    """
-    Parses and plots all the data.
-
-    Args:
-      f: str, The filename of the log whose data to parse and plot.
-
-    Returns:
-      None
-    """
-    self.HandleFile(f)
-    self.Plot(no_binary_in_legend)
-
-  def Plot(self, no_binary_in_legend):
-    """
-    Plots all the data after it's parsed.
-
-    This should only be called after `HandleFile` has been called so that there
-    is actual data to plot.
-    """
-    for key in self.signal:
-      value = self.signal[key]
-
-      # Create a legend label using the binary name (optional), the structure
-      # name and the data search path.
-      label = key[1] + '.' + '.'.join(str(x) for x in key[2])
-      if not no_binary_in_legend:
-        label = key[0] + ' ' + label
-
-      pylab.plot(value.time, value.data, label=label)
-
-    # Set legend font size to small and move it to the top center.
-    fontP = FontProperties()
-    fontP.set_size('small')
-    pylab.legend(bbox_to_anchor=(0.2, 1.10), prop=fontP)
-
-    pylab.show()
-
diff --git a/frc971/control_loops/drivetrain/wheel_nonlinearity_plot.py b/frc971/control_loops/drivetrain/wheel_nonlinearity_plot.py
index 57831c5..9caa9dc 100755
--- a/frc971/control_loops/drivetrain/wheel_nonlinearity_plot.py
+++ b/frc971/control_loops/drivetrain/wheel_nonlinearity_plot.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 #
 # This is a quick script to show the effect of the wheel nonlinearity term on
 # turning rate
diff --git a/frc971/control_loops/python/BUILD b/frc971/control_loops/python/BUILD
index 7851397..880624e 100644
--- a/frc971/control_loops/python/BUILD
+++ b/frc971/control_loops/python/BUILD
@@ -11,7 +11,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -32,7 +32,6 @@
     deps = [
         ":python_init",
         "//external:python-glog",
-        "@slycot_repo//:slycot",
     ],
 )
 
@@ -59,7 +58,7 @@
     deps = [
         ":controls",
         ":python_init",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -72,7 +71,7 @@
     deps = [
         ":controls",
         ":python_init",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -136,7 +135,7 @@
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
         "//y2016/control_loops/python:polydrivetrain_lib",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -149,7 +148,7 @@
         ":controls",
         "//aos/util:py_trapezoid_profile",
         "//frc971/control_loops:python_init",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -162,7 +161,7 @@
         ":controls",
         "//aos/util:py_trapezoid_profile",
         "//frc971/control_loops:python_init",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -184,7 +183,7 @@
         ":basic_window",
         ":libspline",
         ":python_init",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
         "@python_gtk",
     ],
 )
diff --git a/frc971/control_loops/python/angular_system.py b/frc971/control_loops/python/angular_system.py
index 397e6ee..34f4307 100755
--- a/frc971/control_loops/python/angular_system.py
+++ b/frc971/control_loops/python/angular_system.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/frc971/control_loops/python/cim.py b/frc971/control_loops/python/cim.py
index 8c13eb0..f8a6e9a 100644
--- a/frc971/control_loops/python/cim.py
+++ b/frc971/control_loops/python/cim.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 import numpy
diff --git a/frc971/control_loops/python/control_loop.py b/frc971/control_loops/python/control_loop.py
index c3dc1a2..3270f78 100644
--- a/frc971/control_loops/python/control_loop.py
+++ b/frc971/control_loops/python/control_loop.py
@@ -357,7 +357,7 @@
             for y in range(matrix.shape[1]):
                 write_type = repr(matrix[x, y])
                 if scalar_type == 'float':
-                    if '.' not in write_type:
+                    if '.' not in write_type and 'e' not in write_type:
                         write_type += '.0'
                     write_type += 'f'
                 ans.append(
diff --git a/frc971/control_loops/python/controls.py b/frc971/control_loops/python/controls.py
index defdbd2..4fdf1e9 100644
--- a/frc971/control_loops/python/controls.py
+++ b/frc971/control_loops/python/controls.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 """
 Control loop pole placement library.
@@ -10,22 +10,18 @@
 __author__ = 'Austin Schuh (austin.linux@gmail.com)'
 
 import numpy
-import slycot
 import scipy.linalg
+import scipy.signal
 import glog
 
 class Error (Exception):
   """Base class for all control loop exceptions."""
 
 
-class PolePlacementError(Error):
-  """Exception raised when pole placement fails."""
-
-
 # TODO(aschuh): dplace should take a control system object.
 # There should also exist a function to manipulate laplace expressions, and
 # something to plot bode plots and all that.
-def dplace(A, B, poles, alpha=1e-6):
+def dplace(A, B, poles):
   """Set the poles of (A - BF) to poles.
 
   Args:
@@ -34,55 +30,10 @@
     poles: array(imaginary numbers), The poles to use.  Complex conjugates poles
       must be in pairs.
 
-  Raises:
-    ValueError: Arguments were the wrong shape or there were too many poles.
-    PolePlacementError: Pole placement failed.
-
   Returns:
     numpy.matrix(m x n), K
   """
-  # See http://www.icm.tu-bs.de/NICONET/doc/SB01BD.html for a description of the
-  # fortran code that this is cleaning up the interface to.
-  n = A.shape[0]
-  if A.shape[1] != n:
-    raise ValueError("A must be square")
-  if B.shape[0] != n:
-    raise ValueError("B must have the same number of states as A.")
-  m = B.shape[1]
-
-  num_poles = len(poles)
-  if num_poles > n:
-    raise ValueError("Trying to place more poles than states.")
-
-  out = slycot.sb01bd(n=n,
-                      m=m,
-                      np=num_poles,
-                      alpha=alpha,
-                      A=A,
-                      B=B,
-                      w=numpy.array(poles),
-                      dico='D')
-
-  A_z = numpy.matrix(out[0])
-  num_too_small_eigenvalues = out[2]
-  num_assigned_eigenvalues = out[3]
-  num_uncontrollable_eigenvalues = out[4]
-  K = numpy.matrix(-out[5])
-  Z = numpy.matrix(out[6])
-
-  if num_too_small_eigenvalues != 0:
-    raise PolePlacementError("Number of eigenvalues that are too small "
-                             "and are therefore unmodified is %d." %
-                             num_too_small_eigenvalues)
-  if num_assigned_eigenvalues != num_poles:
-    raise PolePlacementError("Did not place all the eigenvalues that were "
-                             "requested. Only placed %d eigenvalues." %
-                             num_assigned_eigenvalues)
-  if num_uncontrollable_eigenvalues != 0:
-    raise PolePlacementError("Found %d uncontrollable eigenvlaues." %
-                             num_uncontrollable_eigenvalues)
-
-  return K
+  return scipy.signal.place_poles(A=A, B=B, poles=numpy.array(poles)).gain_matrix
 
 def c2d(A, B, dt):
   """Converts from continuous time state space representation to discrete time.
@@ -133,9 +84,7 @@
   # P = (A.T * P * A) - (A.T * P * B * numpy.linalg.inv(R + B.T * P *B) * (A.T * P.T * B).T + Q
   # 0.5 * X.T * P * X -> optimal cost to infinity
 
-  P, rcond, w, S, T = slycot.sb02od(
-      n=A.shape[0], m=B.shape[1], A=A, B=B, Q=Q, R=R, dico='D')
-
+  P = scipy.linalg.solve_discrete_are(a=A, b=B, q=Q, r=R)
   F = numpy.linalg.inv(R + B.T * P * B) * B.T * P * A
   if optimal_cost_function:
     return F, P
@@ -164,9 +113,9 @@
                  controllability_rank, n)
 
   # Compute the steady state covariance matrix.
-  P_prior, rcond, w, S, T = slycot.sb02od(n=n, m=m, A=A.T, B=C.T, Q=Q, R=R, dico='D')
+  P_prior = scipy.linalg.solve_discrete_are(a=A.T, b=C.T, q=Q, r=R)
   S = C * P_prior * C.T + R
-  K = numpy.linalg.lstsq(S.T, (P_prior * C.T).T)[0].T
+  K = numpy.linalg.lstsq(S.T, (P_prior * C.T).T, rcond=None)[0].T
   P = (I - K * C) * P_prior
 
   return K, P
diff --git a/frc971/control_loops/python/down_estimator.py b/frc971/control_loops/python/down_estimator.py
index c4b8e7e..224fe03 100644
--- a/frc971/control_loops/python/down_estimator.py
+++ b/frc971/control_loops/python/down_estimator.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import math
 import sys
diff --git a/frc971/control_loops/python/drivetrain.py b/frc971/control_loops/python/drivetrain.py
index a020276..b29e1e6 100644
--- a/frc971/control_loops/python/drivetrain.py
+++ b/frc971/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import controls
diff --git a/frc971/control_loops/python/haptic_wheel.py b/frc971/control_loops/python/haptic_wheel.py
index 79c9c35..5e63df3 100755
--- a/frc971/control_loops/python/haptic_wheel.py
+++ b/frc971/control_loops/python/haptic_wheel.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import controls
diff --git a/frc971/control_loops/python/lib_spline_test.py b/frc971/control_loops/python/lib_spline_test.py
index b4efe72..a1e298a 100644
--- a/frc971/control_loops/python/lib_spline_test.py
+++ b/frc971/control_loops/python/lib_spline_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import math
 import numpy as np
diff --git a/frc971/control_loops/python/libcdd.py b/frc971/control_loops/python/libcdd.py
index b46d908..72d5833 100644
--- a/frc971/control_loops/python/libcdd.py
+++ b/frc971/control_loops/python/libcdd.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 """Wrapper around libcdd, a polytope manipulation library."""
 
@@ -8,24 +8,16 @@
 import os
 import sys
 
-# Wrapper around PyFile_AsFile so that we can print out the error messages.
-# Set the arg type and return types of the function call.
-class FILE(ctypes.Structure):
-  pass
-
-ctypes.pythonapi.PyFile_AsFile.argtypes = [ctypes.py_object]
-ctypes.pythonapi.PyFile_AsFile.restype = ctypes.POINTER(FILE)
-
 # Load and init libcdd.  libcdd is a C library that implements algorithm to
 # manipulate half space and vertex representations of polytopes.
 # Unfortunately, the library was compiled with C++ even though it has a lot of C
 # code in it, so all the symbol names are mangled.  Ug.
 libcdd = None
 for path in os.environ.get('PYTHONPATH').split(':'):
-  try:
-    libcdd = ctypes.cdll.LoadLibrary(os.path.join(path, 'third_party/cddlib/_cddlib.so'))
-  except OSError, e:
-    pass
+    try:
+        libcdd = ctypes.cdll.LoadLibrary(os.path.join(path, 'third_party/cddlib/_cddlib.so'))
+    except OSError:
+        pass
 
 assert libcdd is not None, 'Failed to find _cddlib.so'
 
@@ -41,21 +33,21 @@
 
 # Forward declaration for the polyhedra data structure.
 class dd_polyhedradata(ctypes.Structure):
-  pass
+    pass
 
 
 # Definition of dd_matrixdata
 class dd_matrixdata(ctypes.Structure):
-  _fields_ = [
-      ("rowsize", ctypes.c_long),
-      ("linset", ctypes.POINTER(ctypes.c_ulong)),
-      ("colsize", ctypes.c_long),
-      ("representation", ctypes.c_int),
-      ("numbtype", ctypes.c_int),
-      ("matrix", ctypes.POINTER(ctypes.POINTER(mytype))),
-      ("objective", ctypes.c_int),
-      ("rowvec", ctypes.POINTER(mytype)),
-  ]
+    _fields_ = [
+        ("rowsize", ctypes.c_long),
+        ("linset", ctypes.POINTER(ctypes.c_ulong)),
+        ("colsize", ctypes.c_long),
+        ("representation", ctypes.c_int),
+        ("numbtype", ctypes.c_int),
+        ("matrix", ctypes.POINTER(ctypes.POINTER(mytype))),
+        ("objective", ctypes.c_int),
+        ("rowvec", ctypes.POINTER(mytype)),
+    ]
 
 # Define the input and output types for a bunch of libcdd functions.
 libcdd.dd_CreateMatrix.restype = ctypes.POINTER(dd_matrixdata)
@@ -95,40 +87,40 @@
 
 
 def dd_CreateMatrix(rows, cols):
-  return libcdd.dd_CreateMatrix(ctypes.c_long(rows), ctypes.c_long(cols))
+    return libcdd.dd_CreateMatrix(ctypes.c_long(rows), ctypes.c_long(cols))
 
 
 def dd_set_d(mytype_address, double_value):
-  libcdd.ddd_set_d(mytype_address, ctypes.c_double(double_value))
+    libcdd.ddd_set_d(mytype_address, ctypes.c_double(double_value))
 
 
 def dd_CopyGenerators(polyhedraptr):
-  return libcdd.dd_CopyGenerators(polyhedraptr)
+    return libcdd.dd_CopyGenerators(polyhedraptr)
 
 
 def dd_get_d(mytype_address):
-  return libcdd.ddd_get_d(mytype_address)
+    return libcdd.ddd_get_d(mytype_address)
 
 
 def dd_FreeMatrix(matrixptr):
-  libcdd.dd_FreeMatrix(matrixptr)
+    libcdd.dd_FreeMatrix(matrixptr)
 
 
 def dd_FreePolyhedra(polyhedraptr):
-  libcdd.dd_FreePolyhedra(polyhedraptr)
+    libcdd.dd_FreePolyhedra(polyhedraptr)
 
 
 def dd_DDMatrix2Poly(matrixptr):
-  error = ctypes.c_int()
-  polyhedraptr = libcdd.dd_DDMatrix2Poly(matrixptr, ctypes.byref(error))
+    error = ctypes.c_int()
+    polyhedraptr = libcdd.dd_DDMatrix2Poly(matrixptr, ctypes.byref(error))
 
-  # Return None on error.
-  # The error values are enums, so they aren't exposed.
-  if error.value != DD_NO_ERRORS:
-    # Dump out the errors to stderr
-    libcdd.dd_WriteErrorMessages(
-        ctypes.pythonapi.PyFile_AsFile(ctypes.py_object(sys.stdout)),
-        error)
-    dd_FreePolyhedra(polyhedraptr)
-    return None
-  return polyhedraptr
+    # Return None on error.
+    # The error values are enums, so they aren't exposed.
+    if error.value != DD_NO_ERRORS:
+        # TODO(austin): Dump out the errors to stderr
+        #libcdd.dd_WriteErrorMessages(
+        #    ctypes.pythonapi.PyFile_AsFile(ctypes.py_object(sys.stdout)),
+        #    error)
+        dd_FreePolyhedra(polyhedraptr)
+        return None
+    return polyhedraptr
diff --git a/frc971/control_loops/python/libspline.py b/frc971/control_loops/python/libspline.py
index 9799caa..24f1f51 100755
--- a/frc971/control_loops/python/libspline.py
+++ b/frc971/control_loops/python/libspline.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 """Wrapper around spline.h/cc through spline_array.cc."""
 
 __author__ = 'Alex Perry (alex.perry96@gmail.com)'
diff --git a/frc971/control_loops/python/linear_system.py b/frc971/control_loops/python/linear_system.py
index a2e37f0..9cf49c2 100755
--- a/frc971/control_loops/python/linear_system.py
+++ b/frc971/control_loops/python/linear_system.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/frc971/control_loops/python/polydrivetrain.py b/frc971/control_loops/python/polydrivetrain.py
index 06a182e..2623dda 100644
--- a/frc971/control_loops/python/polydrivetrain.py
+++ b/frc971/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import numpy
 from frc971.control_loops.python import polytope
diff --git a/frc971/control_loops/python/polytope.py b/frc971/control_loops/python/polytope.py
index a1cba57..5aa7ba3 100644
--- a/frc971/control_loops/python/polytope.py
+++ b/frc971/control_loops/python/polytope.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 """
 Polyhedral set library.
@@ -29,7 +29,7 @@
   split_string = s.split('\n')
   width = max(len(stringpiece) for stringpiece in split_string) + 1
 
-  padded_strings = [string.ljust(stringpiece, width, ' ')
+  padded_strings = [stringpiece.ljust(width, ' ')
                         for stringpiece in split_string]
   return padded_strings
 
diff --git a/frc971/control_loops/python/polytope_test.py b/frc971/control_loops/python/polytope_test.py
index f5e4783..68401c2 100755
--- a/frc971/control_loops/python/polytope_test.py
+++ b/frc971/control_loops/python/polytope_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import numpy
 from numpy.testing import *
@@ -153,39 +153,39 @@
     def test_few_constraints_odd_constraint_even_dims_str(self):
         """Tests printing out the set with odd constraints and even dimensions."""
         self.MakePWithDims(num_constraints=5, num_dims=2)
-        self.assertEqual('[[ 0.  0.]            [[ 0.]  \n'
-                         ' [ 0.  0.]  [[x0]      [ 0.]  \n'
-                         ' [ 0.  0.]   [x1]] <=  [ 0.]  \n'
-                         ' [ 0.  0.]             [ 0.]  \n'
-                         ' [ 0.  0.]]            [ 0.]] ',
+        self.assertEqual('[[0. 0.]            [[0.]  \n'
+                         ' [0. 0.]  [[x0]      [0.]  \n'
+                         ' [0. 0.]   [x1]] <=  [0.]  \n'
+                         ' [0. 0.]             [0.]  \n'
+                         ' [0. 0.]]            [0.]] ',
                          str(self.p))
 
     def test_few_constraints_odd_constraint_small_dims_str(self):
         """Tests printing out the set with odd constraints and odd dimensions."""
         self.MakePWithDims(num_constraints=5, num_dims=1)
-        self.assertEqual('[[ 0.]            [[ 0.]  \n'
-                         ' [ 0.]             [ 0.]  \n'
-                         ' [ 0.]  [[x0]] <=  [ 0.]  \n'
-                         ' [ 0.]             [ 0.]  \n'
-                         ' [ 0.]]            [ 0.]] ',
+        self.assertEqual('[[0.]            [[0.]  \n'
+                         ' [0.]             [0.]  \n'
+                         ' [0.]  [[x0]] <=  [0.]  \n'
+                         ' [0.]             [0.]  \n'
+                         ' [0.]]            [0.]] ',
                          str(self.p))
 
     def test_few_constraints_odd_constraint_odd_dims_str(self):
         """Tests printing out the set with odd constraints and odd dimensions."""
         self.MakePWithDims(num_constraints=5, num_dims=3)
-        self.assertEqual('[[ 0.  0.  0.]            [[ 0.]  \n'
-                         ' [ 0.  0.  0.]  [[x0]      [ 0.]  \n'
-                         ' [ 0.  0.  0.]   [x1]  <=  [ 0.]  \n'
-                         ' [ 0.  0.  0.]   [x2]]     [ 0.]  \n'
-                         ' [ 0.  0.  0.]]            [ 0.]] ',
+        self.assertEqual('[[0. 0. 0.]            [[0.]  \n'
+                         ' [0. 0. 0.]  [[x0]      [0.]  \n'
+                         ' [0. 0. 0.]   [x1]  <=  [0.]  \n'
+                         ' [0. 0. 0.]   [x2]]     [0.]  \n'
+                         ' [0. 0. 0.]]            [0.]] ',
                          str(self.p))
 
     def test_many_constraints_even_constraint_odd_dims_str(self):
         """Tests printing out the set with even constraints and odd dimensions."""
         self.MakePWithDims(num_constraints=2, num_dims=3)
-        self.assertEqual('[[ 0.  0.  0.]  [[x0]     [[ 0.]  \n'
-                         ' [ 0.  0.  0.]]  [x1]  <=  [ 0.]] \n'
-                         '                 [x2]]            ',
+        self.assertEqual('[[0. 0. 0.]  [[x0]     [[0.]  \n'
+                         ' [0. 0. 0.]]  [x1]  <=  [0.]] \n'
+                         '              [x2]]           ',
                          str(self.p))
 
 
diff --git a/frc971/control_loops/python/spline.py b/frc971/control_loops/python/spline.py
index 890ef9f..8a0ac04 100644
--- a/frc971/control_loops/python/spline.py
+++ b/frc971/control_loops/python/spline.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 
diff --git a/frc971/control_loops/python/static_zeroing_single_dof_profiled_subsystem_test.py b/frc971/control_loops/python/static_zeroing_single_dof_profiled_subsystem_test.py
index 839677e..8080aff 100644
--- a/frc971/control_loops/python/static_zeroing_single_dof_profiled_subsystem_test.py
+++ b/frc971/control_loops/python/static_zeroing_single_dof_profiled_subsystem_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 # Generates profiled subsystem for use in
 # static_zeroing_single_dof_profiled_subsystem_test
diff --git a/motors/pistol_grip/generate_cogging.py b/motors/pistol_grip/generate_cogging.py
index 9eaf537..d889064 100644
--- a/motors/pistol_grip/generate_cogging.py
+++ b/motors/pistol_grip/generate_cogging.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from matplotlib import pylab
diff --git a/motors/python/BUILD b/motors/python/BUILD
index 4925d4c..90ab608 100644
--- a/motors/python/BUILD
+++ b/motors/python/BUILD
@@ -13,7 +13,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
diff --git a/motors/python/big_phase_current.py b/motors/python/big_phase_current.py
index 6b51704..8cbce3f 100755
--- a/motors/python/big_phase_current.py
+++ b/motors/python/big_phase_current.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 
diff --git a/motors/seems_reasonable/drivetrain.py b/motors/seems_reasonable/drivetrain.py
index f4a5094..52b3920 100644
--- a/motors/seems_reasonable/drivetrain.py
+++ b/motors/seems_reasonable/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import drivetrain
 import sys
@@ -27,7 +27,7 @@
     glog.init()
 
     if len(argv) != 5:
-        print "Expected .h file name and .cc file name"
+        glog.error("Expected .h file name and .cc file name")
     else:
         # Write the generated constants out to a file.
         drivetrain.WriteDrivetrain(
diff --git a/motors/seems_reasonable/polydrivetrain.py b/motors/seems_reasonable/polydrivetrain.py
index a006c67..452b3fb 100644
--- a/motors/seems_reasonable/polydrivetrain.py
+++ b/motors/seems_reasonable/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from motors.seems_reasonable import drivetrain
diff --git a/third_party/matplotlib-cpp/BUILD b/third_party/matplotlib-cpp/BUILD
index 1071824..56fe228 100644
--- a/third_party/matplotlib-cpp/BUILD
+++ b/third_party/matplotlib-cpp/BUILD
@@ -14,7 +14,7 @@
     target_compatible_with = ["@platforms//cpu:x86_64"],
     visibility = ["//visibility:public"],
     deps = [
-        "@python_repo//:python3.5_lib",
+        "@python_repo//:python3.7_lib",
     ],
 )
 
diff --git a/tools/python/runtime_binary.sh b/tools/python/runtime_binary.sh
index 6c96fcb..8380424 100755
--- a/tools/python/runtime_binary.sh
+++ b/tools/python/runtime_binary.sh
@@ -36,8 +36,4 @@
 
 export LD_LIBRARY_PATH="${BASE_PATH}/usr/lib/lapack:${BASE_PATH}/usr/lib/libblas:${BASE_PATH}/usr/lib/x86_64-linux-gnu"
 
-if head -n 1 "$1" | grep -q python3; then
-  exec "$BASE_PATH"/usr/bin/python3 "$@"
-else
-  exec "$BASE_PATH"/usr/bin/python2 "$@"
-fi
+exec "$BASE_PATH"/usr/bin/python3 "$@"
diff --git a/y2012/control_loops/python/drivetrain.py b/y2012/control_loops/python/drivetrain.py
index 7e672e3..5449503 100755
--- a/y2012/control_loops/python/drivetrain.py
+++ b/y2012/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 from frc971.control_loops.python import drivetrain
diff --git a/y2012/control_loops/python/polydrivetrain.py b/y2012/control_loops/python/polydrivetrain.py
index efe4511..ae26dbe 100755
--- a/y2012/control_loops/python/polydrivetrain.py
+++ b/y2012/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from y2012.control_loops.python import drivetrain
diff --git a/y2014/control_loops/python/BUILD b/y2014/control_loops/python/BUILD
index 60488b4..20035bc 100644
--- a/y2014/control_loops/python/BUILD
+++ b/y2014/control_loops/python/BUILD
@@ -60,7 +60,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -76,7 +76,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -92,7 +92,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
diff --git a/y2014/control_loops/python/claw.py b/y2014/control_loops/python/claw.py
index 346a188..77ccf14 100755
--- a/y2014/control_loops/python/claw.py
+++ b/y2014/control_loops/python/claw.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import controls
@@ -136,7 +136,9 @@
         glog.debug('det %s', str(numpy.linalg.det(lstsq_A)))
 
         out_x = numpy.linalg.lstsq(
-            lstsq_A, numpy.matrix([[self.A[1, 2]], [self.A[3, 2]]]))[0]
+            lstsq_A,
+            numpy.matrix([[self.A[1, 2]], [self.A[3, 2]]]),
+            rcond=None)[0]
         self.K[1, 2] = -lstsq_A[0, 0] * (
             self.K[0, 2] - out_x[0]) / lstsq_A[0, 1] + out_x[1]
 
@@ -369,7 +371,7 @@
              iterations=200):
     """Runs the claw plant on a given claw (claw) with an initial condition (initial_X) and goal (goal).
 
-    The tests themselves are not terribly sophisticated; I just test for 
+    The tests themselves are not terribly sophisticated; I just test for
     whether the goal has been reached and whether the separation goes
     outside of the initial and goal values by more than max_separation_error.
     Prints out something for a failure of either condition and returns
diff --git a/y2014/control_loops/python/drivetrain.py b/y2014/control_loops/python/drivetrain.py
index 01877d1..cac236f 100755
--- a/y2014/control_loops/python/drivetrain.py
+++ b/y2014/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 from frc971.control_loops.python import drivetrain
diff --git a/y2014/control_loops/python/extended_lqr.py b/y2014/control_loops/python/extended_lqr.py
index 095a43a..b3f2372 100755
--- a/y2014/control_loops/python/extended_lqr.py
+++ b/y2014/control_loops/python/extended_lqr.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 # This is an initial, hacky implementation of the extended LQR paper. It's just a proof of concept,
 # so don't trust it too much.
diff --git a/y2014/control_loops/python/extended_lqr_derivation.py b/y2014/control_loops/python/extended_lqr_derivation.py
index 248c6fc..010c5de 100755
--- a/y2014/control_loops/python/extended_lqr_derivation.py
+++ b/y2014/control_loops/python/extended_lqr_derivation.py
@@ -1,10 +1,7 @@
-#!/usr/bin/python2
+#!/usr/bin/python3
 
 # This file checks our math in extended_lqr.tex.
 
-from __future__ import print_function
-from __future__ import division
-
 import sys
 import random
 
diff --git a/y2014/control_loops/python/polydrivetrain.py b/y2014/control_loops/python/polydrivetrain.py
index 98910a1..cdaee1b 100755
--- a/y2014/control_loops/python/polydrivetrain.py
+++ b/y2014/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from y2014.control_loops.python import drivetrain
diff --git a/y2014/control_loops/python/polydrivetrain_test.py b/y2014/control_loops/python/polydrivetrain_test.py
index 434cdca..8e0176e 100755
--- a/y2014/control_loops/python/polydrivetrain_test.py
+++ b/y2014/control_loops/python/polydrivetrain_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import polydrivetrain
 import numpy
diff --git a/y2014/control_loops/python/shooter.py b/y2014/control_loops/python/shooter.py
index e25c72b..710233b 100755
--- a/y2014/control_loops/python/shooter.py
+++ b/y2014/control_loops/python/shooter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import gflags
 import glog
@@ -65,11 +65,9 @@
         self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
                                                    self.B_continuous, self.dt)
 
-        self.PlaceControllerPoles([0.45, 0.45])
+        self.PlaceControllerPoles([0.4501, 0.4499])
 
-        self.rpl = .05
-        self.ipl = 0.008
-        self.PlaceObserverPoles([self.rpl, self.rpl])
+        self.PlaceObserverPoles([0.05001, 0.04999])
 
         self.U_max = numpy.matrix([[12.0]])
         self.U_min = numpy.matrix([[-12.0]])
@@ -92,11 +90,9 @@
         self.A, self.B = self.ContinuousToDiscrete(self.A_continuous,
                                                    self.B_continuous, self.dt)
 
-        self.PlaceControllerPoles([0.45, 0.45])
+        self.PlaceControllerPoles([0.45001, 0.44999])
 
-        self.rpl = .05
-        self.ipl = 0.008
-        self.PlaceObserverPoles([self.rpl, self.rpl])
+        self.PlaceObserverPoles([0.05001, 0.04999])
 
         self.U_max = numpy.matrix([[12.0]])
         self.U_min = numpy.matrix([[-12.0]])
diff --git a/y2014_bot3/control_loops/python/drivetrain.py b/y2014_bot3/control_loops/python/drivetrain.py
index 48f2c9e..2990773 100755
--- a/y2014_bot3/control_loops/python/drivetrain.py
+++ b/y2014_bot3/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 from frc971.control_loops.python import drivetrain
diff --git a/y2014_bot3/control_loops/python/polydrivetrain.py b/y2014_bot3/control_loops/python/polydrivetrain.py
index d98e003..08e5583 100755
--- a/y2014_bot3/control_loops/python/polydrivetrain.py
+++ b/y2014_bot3/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from y2014_bot3.control_loops.python import drivetrain
diff --git a/y2014_bot3/control_loops/python/polydrivetrain_test.py b/y2014_bot3/control_loops/python/polydrivetrain_test.py
index 434cdca..8e0176e 100755
--- a/y2014_bot3/control_loops/python/polydrivetrain_test.py
+++ b/y2014_bot3/control_loops/python/polydrivetrain_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import polydrivetrain
 import numpy
diff --git a/y2016/control_loops/python/BUILD b/y2016/control_loops/python/BUILD
index 6c2c02d..3300e62 100644
--- a/y2016/control_loops/python/BUILD
+++ b/y2016/control_loops/python/BUILD
@@ -61,7 +61,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -94,7 +94,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -111,7 +111,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -140,7 +140,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -157,7 +157,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
diff --git a/y2016/control_loops/python/arm.py b/y2016/control_loops/python/arm.py
index 9e6de34..3c47934 100755
--- a/y2016/control_loops/python/arm.py
+++ b/y2016/control_loops/python/arm.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import numpy
 import sys
diff --git a/y2016/control_loops/python/drivetrain.py b/y2016/control_loops/python/drivetrain.py
index ed108ea..8abdd74 100755
--- a/y2016/control_loops/python/drivetrain.py
+++ b/y2016/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 from frc971.control_loops.python import drivetrain
diff --git a/y2016/control_loops/python/intake.py b/y2016/control_loops/python/intake.py
index 6d0509d..7db0b07 100755
--- a/y2016/control_loops/python/intake.py
+++ b/y2016/control_loops/python/intake.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2016/control_loops/python/polydrivetrain.py b/y2016/control_loops/python/polydrivetrain.py
index cdc2881..1b7a0ba 100755
--- a/y2016/control_loops/python/polydrivetrain.py
+++ b/y2016/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from y2016.control_loops.python import drivetrain
diff --git a/y2016/control_loops/python/polydrivetrain_test.py b/y2016/control_loops/python/polydrivetrain_test.py
index 434cdca..8e0176e 100755
--- a/y2016/control_loops/python/polydrivetrain_test.py
+++ b/y2016/control_loops/python/polydrivetrain_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import polydrivetrain
 import numpy
diff --git a/y2016/control_loops/python/shooter.py b/y2016/control_loops/python/shooter.py
index 5e3eddb..70af1b0 100755
--- a/y2016/control_loops/python/shooter.py
+++ b/y2016/control_loops/python/shooter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import controls
diff --git a/y2016/control_loops/python/shoulder.py b/y2016/control_loops/python/shoulder.py
index e17bc7e..7f46b2c 100755
--- a/y2016/control_loops/python/shoulder.py
+++ b/y2016/control_loops/python/shoulder.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import numpy
 import sys
diff --git a/y2016/control_loops/python/wrist.py b/y2016/control_loops/python/wrist.py
index a775494..ca6874b 100755
--- a/y2016/control_loops/python/wrist.py
+++ b/y2016/control_loops/python/wrist.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import controls
diff --git a/y2017/control_loops/python/BUILD b/y2017/control_loops/python/BUILD
index 4de8144..a6277e7 100644
--- a/y2017/control_loops/python/BUILD
+++ b/y2017/control_loops/python/BUILD
@@ -58,7 +58,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -106,7 +106,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -123,7 +123,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
@@ -168,7 +168,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
diff --git a/y2017/control_loops/python/column.py b/y2017/control_loops/python/column.py
index 7c34ee6..0122b58 100755
--- a/y2017/control_loops/python/column.py
+++ b/y2017/control_loops/python/column.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2017/control_loops/python/drivetrain.py b/y2017/control_loops/python/drivetrain.py
index 66adc0a..652fcf4 100755
--- a/y2017/control_loops/python/drivetrain.py
+++ b/y2017/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 from frc971.control_loops.python import drivetrain
diff --git a/y2017/control_loops/python/hood.py b/y2017/control_loops/python/hood.py
index 7f875c9..c405bb2 100755
--- a/y2017/control_loops/python/hood.py
+++ b/y2017/control_loops/python/hood.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2017/control_loops/python/indexer.py b/y2017/control_loops/python/indexer.py
index 0c5cb6b..7312e57 100755
--- a/y2017/control_loops/python/indexer.py
+++ b/y2017/control_loops/python/indexer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import controls
diff --git a/y2017/control_loops/python/intake.py b/y2017/control_loops/python/intake.py
index 015fdfa..ae4f257 100755
--- a/y2017/control_loops/python/intake.py
+++ b/y2017/control_loops/python/intake.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import linear_system
diff --git a/y2017/control_loops/python/polydrivetrain.py b/y2017/control_loops/python/polydrivetrain.py
index 7ddd630..498a2c3 100755
--- a/y2017/control_loops/python/polydrivetrain.py
+++ b/y2017/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from y2017.control_loops.python import drivetrain
diff --git a/y2017/control_loops/python/polydrivetrain_test.py b/y2017/control_loops/python/polydrivetrain_test.py
index 434cdca..8e0176e 100755
--- a/y2017/control_loops/python/polydrivetrain_test.py
+++ b/y2017/control_loops/python/polydrivetrain_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import polydrivetrain
 import numpy
diff --git a/y2017/control_loops/python/shooter.py b/y2017/control_loops/python/shooter.py
index 69f0af4..be4fb81 100755
--- a/y2017/control_loops/python/shooter.py
+++ b/y2017/control_loops/python/shooter.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import controls
@@ -125,7 +125,7 @@
         glog.debug('Poles are %s',
                    repr(numpy.linalg.eig(self.A - self.B * self.K)[0]))
 
-        self.PlaceObserverPoles([0.3, 0.3])
+        self.PlaceObserverPoles([0.299, 0.301])
 
         self.U_max = numpy.matrix([[12.0]])
         self.U_min = numpy.matrix([[-12.0]])
diff --git a/y2017/control_loops/python/turret.py b/y2017/control_loops/python/turret.py
index c5e4269..e67904d 100755
--- a/y2017/control_loops/python/turret.py
+++ b/y2017/control_loops/python/turret.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2018/control_loops/python/BUILD b/y2018/control_loops/python/BUILD
index 1f29601..3c75b97 100644
--- a/y2018/control_loops/python/BUILD
+++ b/y2018/control_loops/python/BUILD
@@ -94,7 +94,7 @@
         "//external:python-gflags",
         "//external:python-glog",
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
diff --git a/y2018/control_loops/python/arm_trajectory.py b/y2018/control_loops/python/arm_trajectory.py
index 3531dc7..bcfd9f0 100755
--- a/y2018/control_loops/python/arm_trajectory.py
+++ b/y2018/control_loops/python/arm_trajectory.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import copy
 import numpy
diff --git a/y2018/control_loops/python/drivetrain.py b/y2018/control_loops/python/drivetrain.py
index 177e9e7..675930a 100644
--- a/y2018/control_loops/python/drivetrain.py
+++ b/y2018/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 from frc971.control_loops.python import drivetrain
diff --git a/y2018/control_loops/python/extended_lqr.py b/y2018/control_loops/python/extended_lqr.py
index 3c8d0e6..4eecee9 100755
--- a/y2018/control_loops/python/extended_lqr.py
+++ b/y2018/control_loops/python/extended_lqr.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 # This is an initial, hacky implementation of the extended LQR paper. It's just
 # a proof of concept, so don't trust it too much.
 
diff --git a/y2018/control_loops/python/intake.py b/y2018/control_loops/python/intake.py
index 3802505..00f737b 100755
--- a/y2018/control_loops/python/intake.py
+++ b/y2018/control_loops/python/intake.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import controls
diff --git a/y2018/control_loops/python/path_points.py b/y2018/control_loops/python/path_points.py
index 319853c..eed9804 100644
--- a/y2018/control_loops/python/path_points.py
+++ b/y2018/control_loops/python/path_points.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 # Path points from Parker.
 # Points in (theta0, theta1) of the boundry of the safe region.
diff --git a/y2018/control_loops/python/polydrivetrain.py b/y2018/control_loops/python/polydrivetrain.py
index 1652b99..c92e432 100644
--- a/y2018/control_loops/python/polydrivetrain.py
+++ b/y2018/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from y2018.control_loops.python import drivetrain
diff --git a/y2018/control_loops/python/polydrivetrain_test.py b/y2018/control_loops/python/polydrivetrain_test.py
index 02018d3..ccc3b77 100644
--- a/y2018/control_loops/python/polydrivetrain_test.py
+++ b/y2018/control_loops/python/polydrivetrain_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import polydrivetrain
 import numpy
diff --git a/y2018/control_loops/python/spline_generate.py b/y2018/control_loops/python/spline_generate.py
index 4376f37..cea3d7e 100644
--- a/y2018/control_loops/python/spline_generate.py
+++ b/y2018/control_loops/python/spline_generate.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python

+#!/usr/bin/python3

 import numpy as np

 import matplotlib.pyplot as plt

 from frc971.control_loops.python import drivetrain

diff --git a/y2019/control_loops/python/drivetrain.py b/y2019/control_loops/python/drivetrain.py
index 0a1a90d..9c4db49 100644
--- a/y2019/control_loops/python/drivetrain.py
+++ b/y2019/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 from frc971.control_loops.python import drivetrain
diff --git a/y2019/control_loops/python/elevator.py b/y2019/control_loops/python/elevator.py
index 0a4e208..b7fc74b 100755
--- a/y2019/control_loops/python/elevator.py
+++ b/y2019/control_loops/python/elevator.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import linear_system
diff --git a/y2019/control_loops/python/intake.py b/y2019/control_loops/python/intake.py
index 9f4f488..828f54e 100755
--- a/y2019/control_loops/python/intake.py
+++ b/y2019/control_loops/python/intake.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2019/control_loops/python/polydrivetrain.py b/y2019/control_loops/python/polydrivetrain.py
index fe52859..eaccb92 100644
--- a/y2019/control_loops/python/polydrivetrain.py
+++ b/y2019/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from y2019.control_loops.python import drivetrain
diff --git a/y2019/control_loops/python/stilts.py b/y2019/control_loops/python/stilts.py
index 2c4644d..c2bbe68 100755
--- a/y2019/control_loops/python/stilts.py
+++ b/y2019/control_loops/python/stilts.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from frc971.control_loops.python import linear_system
diff --git a/y2019/control_loops/python/wrist.py b/y2019/control_loops/python/wrist.py
index 127def7..c983a1d 100755
--- a/y2019/control_loops/python/wrist.py
+++ b/y2019/control_loops/python/wrist.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2019/vision/undistort.py b/y2019/vision/undistort.py
index 9a35d9c..8e1cbd2 100755
--- a/y2019/vision/undistort.py
+++ b/y2019/vision/undistort.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import cv2
 import glob
diff --git a/y2020/control_loops/python/BUILD b/y2020/control_loops/python/BUILD
index 34d49cb..9886452 100644
--- a/y2020/control_loops/python/BUILD
+++ b/y2020/control_loops/python/BUILD
@@ -121,7 +121,7 @@
     target_compatible_with = ["@platforms//cpu:x86_64"],
     deps = [
         "//frc971/control_loops/python:controls",
-        "@matplotlib_repo//:matplotlib2.7",
+        "@matplotlib_repo//:matplotlib3",
     ],
 )
 
diff --git a/y2020/control_loops/python/accelerator.py b/y2020/control_loops/python/accelerator.py
index 48c9098..bb50d9d 100644
--- a/y2020/control_loops/python/accelerator.py
+++ b/y2020/control_loops/python/accelerator.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from y2020.control_loops.python import flywheel
diff --git a/y2020/control_loops/python/control_panel.py b/y2020/control_loops/python/control_panel.py
index e02aa13..7a5bdf9 100644
--- a/y2020/control_loops/python/control_panel.py
+++ b/y2020/control_loops/python/control_panel.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2020/control_loops/python/drivetrain.py b/y2020/control_loops/python/drivetrain.py
index 84bbb66..f5c6214 100644
--- a/y2020/control_loops/python/drivetrain.py
+++ b/y2020/control_loops/python/drivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from __future__ import print_function
 from frc971.control_loops.python import drivetrain
diff --git a/y2020/control_loops/python/finisher.py b/y2020/control_loops/python/finisher.py
index 9381630..6da1c03 100644
--- a/y2020/control_loops/python/finisher.py
+++ b/y2020/control_loops/python/finisher.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from frc971.control_loops.python import control_loop
 from y2020.control_loops.python import flywheel
diff --git a/y2020/control_loops/python/hood.py b/y2020/control_loops/python/hood.py
index 7e75dce..d8118a1 100644
--- a/y2020/control_loops/python/hood.py
+++ b/y2020/control_loops/python/hood.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
@@ -17,7 +17,6 @@
 except gflags.DuplicateFlagError:
     pass
 
-
 # Hood is an angular subsystem due to the mounting of the encoder on the hood
 # joint.  We are currently electing to ignore potential non-linearity.
 
@@ -47,7 +46,7 @@
         angular_system.PlotKick(kHood, R)
         angular_system.PlotMotion(kHood, R)
 
-    glog.info("Radians per turn: %f\n", radians_per_turn)
+    glog.debug("Radians per turn: %f\n", radians_per_turn)
 
     # Write the generated constants out to a file.
     if len(argv) != 5:
diff --git a/y2020/control_loops/python/intake.py b/y2020/control_loops/python/intake.py
index bb19e2d..de60b28 100644
--- a/y2020/control_loops/python/intake.py
+++ b/y2020/control_loops/python/intake.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2020/control_loops/python/polydrivetrain.py b/y2020/control_loops/python/polydrivetrain.py
index 6481af1..dad06b0 100644
--- a/y2020/control_loops/python/polydrivetrain.py
+++ b/y2020/control_loops/python/polydrivetrain.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 import sys
 from y2020.control_loops.python import drivetrain
diff --git a/y2020/control_loops/python/turret.py b/y2020/control_loops/python/turret.py
index 5a27afb..acddae3 100644
--- a/y2020/control_loops/python/turret.py
+++ b/y2020/control_loops/python/turret.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 
 from aos.util.trapezoid_profile import TrapezoidProfile
 from frc971.control_loops.python import control_loop
diff --git a/y2020/vision/sift/demo_sift_training.py b/y2020/vision/sift/demo_sift_training.py
index c78a44a..e8d3521 100644
--- a/y2020/vision/sift/demo_sift_training.py
+++ b/y2020/vision/sift/demo_sift_training.py
@@ -16,7 +16,7 @@
 
   image = cv2.cvtColor(image4_cleaned, cv2.COLOR_BGR2GRAY)
   image = cv2.resize(image, (640, 480))
-  sift = cv2.xfeatures2d.SIFT_create()
+  sift = cv2.SIFT_create()
   keypoints, descriptors = sift.detectAndCompute(image, None)
 
   fbb = flatbuffers.Builder(0)
diff --git a/y2020/vision/tools/python_code/load_sift_training.py b/y2020/vision/tools/python_code/load_sift_training.py
index 809d748..402e468 100644
--- a/y2020/vision/tools/python_code/load_sift_training.py
+++ b/y2020/vision/tools/python_code/load_sift_training.py
@@ -52,9 +52,9 @@
 
     output_path = sys.argv[1]
 
-    if (len(sys.argv) > 2):
+    if len(sys.argv) > 2:
         if sys.argv[2] == "test":
-            glog.info("Loading test data")
+            glog.debug("Loading test data")
             import camera_definition_test
             import target_definition_test
             target_data_list = target_definition_test.compute_target_definition(
@@ -64,7 +64,7 @@
             glog.error("Unhandled arguments: '%s'" % sys.argv[2])
             quit()
     else:
-        glog.info("Loading target configuration data")
+        glog.debug("Loading target configuration data")
         import camera_definition
         import target_definition
         target_data_list = target_definition.compute_target_definition()
diff --git a/y2020/vision/tools/python_code/train_and_match.py b/y2020/vision/tools/python_code/train_and_match.py
index 1def399..7a9d9bf 100644
--- a/y2020/vision/tools/python_code/train_and_match.py
+++ b/y2020/vision/tools/python_code/train_and_match.py
@@ -54,7 +54,7 @@
 def load_feature_extractor():
     if FEATURE_EXTRACTOR_NAME is 'SIFT':
         # Initiate SIFT detector
-        feature_extractor = cv2.xfeatures2d.SIFT_create()
+        feature_extractor = cv2.SIFT_create()
     elif FEATURE_EXTRACTOR_NAME is 'SURF':
         # Initiate SURF detector
         feature_extractor = cv2.xfeatures2d.SURF_create()