From 53f4fd61bc7eb41fe29b77cc13f0d76f4038361b Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauldmccarthy@gmail.com>
Date: Wed, 4 Mar 2020 12:16:49 +0000
Subject: [PATCH] tweaks to intro pracs

---
 getting_started/01_basics.ipynb          | 1174 +++++-----------------
 getting_started/01_basics.md             |   95 +-
 getting_started/02_text_io.ipynb         |  157 ++-
 getting_started/02_text_io.md            |  161 ++-
 getting_started/03_file_management.ipynb |   32 +-
 getting_started/03_file_management.md    |   32 +-
 getting_started/04_numpy.ipynb           |   17 +-
 getting_started/04_numpy.md              |   17 +-
 8 files changed, 645 insertions(+), 1040 deletions(-)

diff --git a/getting_started/01_basics.ipynb b/getting_started/01_basics.ipynb
index 11cc06d..677b36d 100644
--- a/getting_started/01_basics.ipynb
+++ b/getting_started/01_basics.ipynb
@@ -16,7 +16,17 @@
     "(including the text blocks, so you can just move down the document\n",
     "with shift + enter).\n",
     "\n",
-    "It is also possible to _change_ the contents of each code block (these pages are completely interactive) so do experiment with the code you see and try some variations!\n",
+    "It is also possible to _change_ the contents of each code block (these pages\n",
+    "are completely interactive) so do experiment with the code you see and try\n",
+    "some variations!\n",
+    "\n",
+    "> **Important**: We are exclusively using Python 3 in FSL - as of FSL 6.0.4 we\n",
+    "> are using Python 3.7. There are some subtle differences between Python 2 and\n",
+    "> Python 3, but instead of learning about these differences, it is easier to\n",
+    "> simply forget that Python 2 exists.  When you are googling for Python help,\n",
+    "> make sure that the pages you find are relevant to Python 3 and *not* Python\n",
+    "> 2! The official Python docs can be found at https://docs.python.org/3/ (note\n",
+    "> the _/3/_ at the end!).\n",
     "\n",
     "## Contents\n",
     "\n",
@@ -64,17 +74,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 130,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "4\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = 4\n",
     "b = 3.6\n",
@@ -93,19 +95,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 131,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[10, 20, 30]\n",
-      "{'b': 20, 'a': 10}\n",
-      "4 3.6 abc\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(d)\n",
     "print(e)\n",
@@ -135,17 +127,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 132,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "test string  ::  another test string\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "s1 = \"test string\"\n",
     "s2 = 'another test string'\n",
@@ -161,20 +145,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 133,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "This is\n",
-      "a string over\n",
-      "multiple lines\n",
-      "\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "s3 = '''This is\n",
     "a string over\n",
@@ -190,23 +163,16 @@
     "<a class=\"anchor\" id=\"Format\"></a>\n",
     "### Format\n",
     "\n",
-    "More interesting strings can be created using the `format` statement, which is very useful in print statements:"
+    "More interesting strings can be created using the\n",
+    "[`format`](https://docs.python.org/3/library/string.html#formatstrings)\n",
+    "statement, which is very useful in print statements:"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 134,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "The numerical value is 1 and a name is PyTreat\n",
-      "A name is PyTreat and a number is 1\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "x = 1\n",
     "y = 'PyTreat'\n",
@@ -219,8 +185,27 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "There are also other options along these lines, but this is the more modern version, although you will see plenty of the other alternatives in \"old\" code (to python coders this means anything written before last week).\n",
-    "\n",
+    "Python also supports C-style [`%`\n",
+    "formatting](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "x = 1\n",
+    "y = 'PyTreat'\n",
+    "s = 'The numerical value is %i and a name is %s' % (x, y)\n",
+    "print(s)\n",
+    "print('A name is %s and a number is %i' % (y, x))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
     "<a class=\"anchor\" id=\"String-manipulation\"></a>\n",
     "### String manipulation\n",
     "\n",
@@ -229,18 +214,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 135,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "THIS IS A TEST STRING\n",
-      "this is a test string\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "s = 'This is a Test String'\n",
     "print(s.upper())\n",
@@ -256,17 +232,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 136,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "This is a Better String\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "s = 'This is a Test String'\n",
     "s2 = s.replace('Test', 'Better')\n",
@@ -282,17 +250,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 137,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "This is a Test String :: This is a Better String\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "s3 = s + ' :: ' + s2\n",
     "print(s3)"
@@ -307,17 +267,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 138,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "This is an example of an example String\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "import re\n",
     "s = 'This is a test of a Test String'\n",
@@ -333,23 +285,15 @@
     "\n",
     "For more information on matching and substitutions, look up the regular expression module on the web.\n",
     "\n",
-    "Two common and convenient string methods are `strip()` and `split()`.  The first will remove any whitespace at the beginning and end of a string:"
+    "Two common and convenient string methods are `strip()` and `split()`.  The\n",
+    "first will remove any whitespace at the beginning and end of a string:"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 139,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "*   A very    spacy   string       *\n",
-      "*A very    spacy   string*\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "s2 = '   A very    spacy   string       '\n",
     "print('*' + s2 + '*')\n",
@@ -365,18 +309,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 140,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "['This', 'is', 'a', 'test', 'of', 'a', 'Test', 'String']\n",
-      "['A', 'very', 'spacy', 'string']\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(s.split())\n",
     "print(s2.split())"
@@ -391,17 +326,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 141,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "['  This is', '  as you can    see ', '   a very  weirdly spaced and punctuated    string ...  ']\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "s4 = '  This is,  as you can    see ,   a very  weirdly spaced and punctuated    string ...  '\n",
     "print(s4.split(','))"
@@ -411,28 +338,44 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "There are more powerful ways of dealing with this like csv files/strings, which are covered in later practicals, but even this can get you a long way.\n",
+    "A neat trick, if you want to change the delimiter in some structured data (e.g.\n",
+    "replace `,` with `\\t`), is to use `split()` in combination with another string\n",
+    "method, `join()`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "csvdata = 'some,comma,separated,data'\n",
+    "tsvdata = '\\t'.join(csvdata.split(','))\n",
+    "tsvdata = tsvdata.replace('comma', 'tab'))\n",
+    "print('csvdata:', csvdata)\n",
+    "print('tsvdata:', tsvdata)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "There are more powerful ways of dealing with this like csv files/strings,\n",
+    "which are covered in later practicals, but even this can get you a long way.\n",
     "\n",
-    "> Note that strings in python 3 are _unicode_ so can represent Chinese characters, etc, and is therefore very flexible.  However, in general you can just be blissfully ignorant of this fact.\n",
+    "> Note that strings in python 3 are _unicode_ so can represent Chinese\n",
+    "> characters, etc, and is therefore very flexible.  However, in general you\n",
+    "> can just be blissfully ignorant of this fact.\n",
     "\n",
-    "Strings can be converted to integer or floating-point values by using the `int()` and `float()` calls:"
+    "Strings can be converted to integer or floating-point values by using the\n",
+    "`int()` and `float()` calls:"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 142,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "232.03\n",
-      "25.03\n",
-      "25.03\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "sint='23'\n",
     "sfp='2.03'\n",
@@ -452,8 +395,8 @@
     "<a class=\"anchor\" id=\"Tuples-and-lists\"></a>\n",
     "## Tuples and lists\n",
     "\n",
-    "Both tuples and lists are builtin python types and are like vectors, \n",
-    "but for numerical vectors and arrays it is much better to use _numpy_\n",
+    "Both tuples and lists are builtin python types and are like vectors,\n",
+    "but for numerical vectors and arrays it is much better to use `numpy`\n",
     "arrays (or matrices), which are covered in a later tutorial.\n",
     "\n",
     "A tuple is like a list or a vector, but with less flexibility than a full list (tuples are immutable), however anything can be stored in either a list or tuple, without any consistency being required.  Tuples are defined using round brackets and lists are defined using square brackets. For example:"
@@ -461,18 +404,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 143,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "(3, 7.6, 'str')\n",
-      "[1, 'mj', -5.4]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "xtuple = (3, 7.6, 'str')\n",
     "xlist = [1, 'mj', -5.4]\n",
@@ -489,18 +423,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 144,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "x2 is:  ((3, 7.6, 'str'), [1, 'mj', -5.4])\n",
-      "x3 is:  [(3, 7.6, 'str'), [1, 'mj', -5.4]]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "x2 = (xtuple, xlist)\n",
     "x3 = [xtuple, xlist]\n",
@@ -520,17 +445,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 145,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[10, 20, 30, 70, 80]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = [10, 20, 30]\n",
     "a = a + [70]\n",
@@ -542,7 +459,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "> Similar things can be done for tuples, except for the last one: that is, a += (80) as a tuple is immutable so cannot be changed like this. \n",
+    "> Similar things can be done for tuples, except for the last one: that is,\n",
+    "> `a += (80)` as a tuple is immutable so cannot be changed like this.\n",
     "\n",
     "<a class=\"anchor\" id=\"Indexing\"></a>\n",
     "### Indexing\n",
@@ -552,17 +470,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 146,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "20\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "d = [10, 20, 30]\n",
     "print(d[1])"
@@ -578,18 +488,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 147,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "10\n",
-      "30\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = [10, 20, 30, 40, 50, 60]\n",
     "print(a[0])\n",
@@ -600,23 +501,15 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Indices naturally run from 0 to N-1, _but_ negative numbers can be used to reference from the end (circular wrap-around)."
+    "Indices naturally run from 0 to N-1, _but_ negative numbers can be used to\n",
+    "reference from the end (circular wrap-around)."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 148,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "60\n",
-      "10\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(a[-1])\n",
     "print(a[-6])"
@@ -631,42 +524,18 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 149,
-   "metadata": {},
-   "outputs": [
-    {
-     "ename": "IndexError",
-     "evalue": "list index out of range",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mIndexError\u001b[0m                                Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-149-f4cf4536701c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m7\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-      "\u001b[0;31mIndexError\u001b[0m: list index out of range"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(a[-7])"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 150,
-   "metadata": {},
-   "outputs": [
-    {
-     "ename": "IndexError",
-     "evalue": "list index out of range",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mIndexError\u001b[0m                                Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-150-52d95fbe5286>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m6\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-      "\u001b[0;31mIndexError\u001b[0m: list index out of range"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(a[6])"
    ]
@@ -680,17 +549,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 151,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "6\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(len(a))"
    ]
@@ -704,18 +565,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 152,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "20\n",
-      "40\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "b = [[10, 20, 30], [40, 50, 60]]\n",
     "print(b[0][1])\n",
@@ -739,17 +591,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 153,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[10, 20, 30]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(a[0:3])"
    ]
@@ -766,18 +610,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 154,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[10, 20, 30]\n",
-      "[20, 30]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = [10, 20, 30, 40, 50, 60]\n",
     "print(a[0:3])    # same as a(1:3) in MATLAB\n",
@@ -791,26 +626,14 @@
     "> _*Pitfall:*_\n",
     ">\n",
     ">  Unlike in MATLAB, you cannot use a list as indices instead of an\n",
-    ">  integer or a slice (although these can be done in _numpy_)."
+    ">  integer or a slice (although these can be done in `numpy`)."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 155,
-   "metadata": {},
-   "outputs": [
-    {
-     "ename": "TypeError",
-     "evalue": "list indices must be integers or slices, not list",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
-      "\u001b[0;32m<ipython-input-155-aad7915ae3d8>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mb\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-      "\u001b[0;31mTypeError\u001b[0m: list indices must be integers or slices, not list"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "b = [3, 4]\n",
     "print(a[b])"
@@ -825,19 +648,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 156,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[10, 20, 30]\n",
-      "[20, 30, 40, 50, 60]\n",
-      "[10, 20, 30, 40, 50]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(a[:3])\n",
     "print(a[1:])\n",
@@ -855,19 +668,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 157,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[10, 30]\n",
-      "[10, 30, 50]\n",
-      "[60, 50, 40, 30, 20, 10]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print(a[0:4:2])\n",
     "print(a[::2])\n",
@@ -889,17 +692,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 158,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[10, 20, 30, 10, 20, 30, 10, 20, 30, 10, 20, 30]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "d = [10, 20, 30]\n",
     "print(d * 4)"
@@ -914,21 +709,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 159,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[10, 20, 30, 40]\n",
-      "[10, 20, 30, 40, 50, 60]\n",
-      "[10, 20, 30, 40, 50, 60, 70, 80]\n",
-      "[10, 30, 40, 50, 60, 70, 80]\n",
-      "[30, 40, 50, 60, 70, 80]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "d.append(40)\n",
     "print(d)\n",
@@ -954,19 +737,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 160,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "10\n",
-      "20\n",
-      "30\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "d = [10, 20, 30]\n",
     "for x in d:\n",
@@ -987,134 +760,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 161,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Help on list object:\n",
-      "\n",
-      "class list(object)\n",
-      " |  list() -> new empty list\n",
-      " |  list(iterable) -> new list initialized from iterable's items\n",
-      " |  \n",
-      " |  Methods defined here:\n",
-      " |  \n",
-      " |  __add__(self, value, /)\n",
-      " |      Return self+value.\n",
-      " |  \n",
-      " |  __contains__(self, key, /)\n",
-      " |      Return key in self.\n",
-      " |  \n",
-      " |  __delitem__(self, key, /)\n",
-      " |      Delete self[key].\n",
-      " |  \n",
-      " |  __eq__(self, value, /)\n",
-      " |      Return self==value.\n",
-      " |  \n",
-      " |  __ge__(self, value, /)\n",
-      " |      Return self>=value.\n",
-      " |  \n",
-      " |  __getattribute__(self, name, /)\n",
-      " |      Return getattr(self, name).\n",
-      " |  \n",
-      " |  __getitem__(...)\n",
-      " |      x.__getitem__(y) <==> x[y]\n",
-      " |  \n",
-      " |  __gt__(self, value, /)\n",
-      " |      Return self>value.\n",
-      " |  \n",
-      " |  __iadd__(self, value, /)\n",
-      " |      Implement self+=value.\n",
-      " |  \n",
-      " |  __imul__(self, value, /)\n",
-      " |      Implement self*=value.\n",
-      " |  \n",
-      " |  __init__(self, /, *args, **kwargs)\n",
-      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
-      " |  \n",
-      " |  __iter__(self, /)\n",
-      " |      Implement iter(self).\n",
-      " |  \n",
-      " |  __le__(self, value, /)\n",
-      " |      Return self<=value.\n",
-      " |  \n",
-      " |  __len__(self, /)\n",
-      " |      Return len(self).\n",
-      " |  \n",
-      " |  __lt__(self, value, /)\n",
-      " |      Return self<value.\n",
-      " |  \n",
-      " |  __mul__(self, value, /)\n",
-      " |      Return self*value.n\n",
-      " |  \n",
-      " |  __ne__(self, value, /)\n",
-      " |      Return self!=value.\n",
-      " |  \n",
-      " |  __new__(*args, **kwargs) from builtins.type\n",
-      " |      Create and return a new object.  See help(type) for accurate signature.\n",
-      " |  \n",
-      " |  __repr__(self, /)\n",
-      " |      Return repr(self).\n",
-      " |  \n",
-      " |  __reversed__(...)\n",
-      " |      L.__reversed__() -- return a reverse iterator over the list\n",
-      " |  \n",
-      " |  __rmul__(self, value, /)\n",
-      " |      Return self*value.\n",
-      " |  \n",
-      " |  __setitem__(self, key, value, /)\n",
-      " |      Set self[key] to value.\n",
-      " |  \n",
-      " |  __sizeof__(...)\n",
-      " |      L.__sizeof__() -- size of L in memory, in bytes\n",
-      " |  \n",
-      " |  append(...)\n",
-      " |      L.append(object) -> None -- append object to end\n",
-      " |  \n",
-      " |  clear(...)\n",
-      " |      L.clear() -> None -- remove all items from L\n",
-      " |  \n",
-      " |  copy(...)\n",
-      " |      L.copy() -> list -- a shallow copy of L\n",
-      " |  \n",
-      " |  count(...)\n",
-      " |      L.count(value) -> integer -- return number of occurrences of value\n",
-      " |  \n",
-      " |  extend(...)\n",
-      " |      L.extend(iterable) -> None -- extend list by appending elements from the iterable\n",
-      " |  \n",
-      " |  index(...)\n",
-      " |      L.index(value, [start, [stop]]) -> integer -- return first index of value.\n",
-      " |      Raises ValueError if the value is not present.\n",
-      " |  \n",
-      " |  insert(...)\n",
-      " |      L.insert(index, object) -- insert object before index\n",
-      " |  \n",
-      " |  pop(...)\n",
-      " |      L.pop([index]) -> item -- remove and return item at index (default last).\n",
-      " |      Raises IndexError if list is empty or index is out of range.\n",
-      " |  \n",
-      " |  remove(...)\n",
-      " |      L.remove(value) -> None -- remove first occurrence of value.\n",
-      " |      Raises ValueError if the value is not present.\n",
-      " |  \n",
-      " |  reverse(...)\n",
-      " |      L.reverse() -- reverse *IN PLACE*\n",
-      " |  \n",
-      " |  sort(...)\n",
-      " |      L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*\n",
-      " |  \n",
-      " |  ----------------------------------------------------------------------\n",
-      " |  Data and other attributes defined here:\n",
-      " |  \n",
-      " |  __hash__ = None\n",
-      "\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "help(d)"
    ]
@@ -1128,64 +776,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 162,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "['__add__',\n",
-       " '__class__',\n",
-       " '__contains__',\n",
-       " '__delattr__',\n",
-       " '__delitem__',\n",
-       " '__dir__',\n",
-       " '__doc__',\n",
-       " '__eq__',\n",
-       " '__format__',\n",
-       " '__ge__',\n",
-       " '__getattribute__',\n",
-       " '__getitem__',\n",
-       " '__gt__',\n",
-       " '__hash__',\n",
-       " '__iadd__',\n",
-       " '__imul__',\n",
-       " '__init__',\n",
-       " '__iter__',\n",
-       " '__le__',\n",
-       " '__len__',\n",
-       " '__lt__',\n",
-       " '__mul__',\n",
-       " '__ne__',\n",
-       " '__new__',\n",
-       " '__reduce__',\n",
-       " '__reduce_ex__',\n",
-       " '__repr__',\n",
-       " '__reversed__',\n",
-       " '__rmul__',\n",
-       " '__setattr__',\n",
-       " '__setitem__',\n",
-       " '__sizeof__',\n",
-       " '__str__',\n",
-       " '__subclasshook__',\n",
-       " 'append',\n",
-       " 'clear',\n",
-       " 'copy',\n",
-       " 'count',\n",
-       " 'extend',\n",
-       " 'index',\n",
-       " 'insert',\n",
-       " 'pop',\n",
-       " 'remove',\n",
-       " 'reverse',\n",
-       " 'sort']"
-      ]
-     },
-     "execution_count": 162,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "dir(d)"
    ]
@@ -1194,7 +787,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "> Note that google is often more helpful!  At least, as long as you find pages relating to the right version of python - we use python 3 for FSL, so check that what you find is appropriate for that.\n",
+    "> Note that google is often more helpful!  At least, as long as you find pages\n",
+    "> relating to Python 3 - Python 2 is no longer supported, but there is still\n",
+    "> lots of information about it on the internet, so be careful!\n",
     "\n",
     "---\n",
     "\n",
@@ -1206,20 +801,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 163,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "2\n",
-      "dict_keys(['b', 'a'])\n",
-      "dict_values([20, 10])\n",
-      "10\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "e = {'a' : 10, 'b': 20}\n",
     "print(len(e))\n",
@@ -1244,17 +828,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 164,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "{'b': 20, 'a': 10, 'c': 555}\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "e['c'] = 555   # just like in Biobank!  ;)\n",
     "print(e)"
@@ -1272,18 +848,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 165,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "{'a': 10, 'c': 555}\n",
-      "{'a': 10}\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "e.pop('b')\n",
     "print(e)\n",
@@ -1303,19 +870,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 166,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "('b', 20)\n",
-      "('a', 10)\n",
-      "('c', 555)\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "e = {'a' : 10, 'b': 20, 'c':555}\n",
     "for k, v in e.items():\n",
@@ -1333,19 +890,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 167,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "('b', 20)\n",
-      "('a', 10)\n",
-      "('c', 555)\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "for k in e:\n",
     "    print((k, e[k]))"
@@ -1355,31 +902,29 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "> Note that in both cases the order is arbitrary. The `sorted` function can be used if you want keys in a sorted order; e.g. `for k in sorted(e):` ...\n",
+    "> In older versions of Python 3, there was no guarantee of ordering when using dictionaries.\n",
+    "> However, a of Python 3.7, dictionaries will remember the order in which items are inserted,\n",
+    "> and the `keys()`, `values()`, and `items()` methods will return elements in that order.\n",
     ">\n",
-    "> There are also [other options](https://docs.python.org/3.5/library/collections.html#collections.OrderedDict) if you want a dictionary with ordering.\n",
+    "\n",
+    "> If you want a dictionary with ordering, *and* you want your code to work with\n",
+    "> Python versions older than 3.7, you can use the\n",
+    "> [`OrderedDict`](https://docs.python.org/3/library/collections.html#collections.OrderedDict)\n",
+    "> class.\n",
     "\n",
     "---\n",
     "\n",
     "<a class=\"anchor\" id=\"Copying-and-references\"></a>\n",
-    "## Copying and references \n",
+    "## Copying and references\n",
     "\n",
     "In python there are immutable types (e.g. numbers) and mutable types (e.g. lists). The main thing to know is that assignment can sometimes create separate copies and sometimes create references (as in C++). In general, the more complicated types are assigned via references. For example:"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 168,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "7\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = 7\n",
     "b = a\n",
@@ -1396,17 +941,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 169,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[8888]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = [7]\n",
     "b = a\n",
@@ -1423,17 +960,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 170,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[7, 7]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = [7]\n",
     "b = a * 2\n",
@@ -1450,17 +979,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 171,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[7]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = [7]\n",
     "b = list(a)\n",
@@ -1477,18 +998,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 172,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "(2, 5, 7)\n",
-      "[2, 5, 7]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "xt = (2, 5, 7)\n",
     "xl = list(xt)\n",
@@ -1507,24 +1019,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 173,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "a:  [5]\n",
-      "x:  [5, 10]\n",
-      "a:  [5, 10]\n",
-      "x:  [5, 10, 10]\n",
-      "a:  [5, 10]\n",
-      "return value:  [5, 10, 10]\n",
-      "a:  [5, 10]\n",
-      "b:  [5, 10, 10]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "def foo1(x):\n",
     "    x.append(10)\n",
@@ -1563,7 +1060,11 @@
     "<a class=\"anchor\" id=\"Boolean-operators\"></a>\n",
     "### Boolean operators\n",
     "\n",
-    "There is a boolean type in python that can be `True` or `False` (note the capitals). Other values can also be used for True or False (e.g., 1 for True; 0 or None or [] or {} or \"\") although they are not considered 'equal' in the sense that the operator `==` would consider them the same.\n",
+    "There is a boolean type in python that can be `True` or `False` (note the\n",
+    "capitals). Other values can also be used for True or False (e.g., `1` for\n",
+    "`True`; `0` or `None` or `[]` or `{}` or `\"\"` for `False`) although they are\n",
+    "not considered 'equal' in the sense that the operator `==` would consider them\n",
+    "the same.\n",
     "\n",
     "Relevant boolean and comparison operators include: `not`, `and`, `or`, `==` and `!=`\n",
     "\n",
@@ -1572,21 +1073,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 174,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Not a is: False\n",
-      "Not 1 is: False\n",
-      "Not 0 is: True\n",
-      "Not {} is: True\n",
-      "{}==0 is: False\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = True\n",
     "print('Not a is:', not a)\n",
@@ -1605,19 +1094,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 175,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "False\n",
-      "True\n",
-      "True\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "print('the' in 'a number of words')\n",
     "print('of' in 'a number of words')\n",
@@ -1639,18 +1118,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 176,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "0.5890515724950383\n",
-      "Positive\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "import random\n",
     "a = random.uniform(-1, 1)\n",
@@ -1672,17 +1142,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 177,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Variable is true, or at least not empty\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "a = []    # just one of many examples\n",
     "if not a:\n",
@@ -1693,7 +1155,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "This can be useful for functions where a variety of possible input types are being dealt with. \n",
+    "This can be useful for functions where a variety of possible input types are being dealt with.\n",
     "\n",
     "---\n",
     "\n",
@@ -1705,21 +1167,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 178,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "2\n",
-      "is\n",
-      "more\n",
-      "than\n",
-      "1\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "for x in [2, 'is', 'more', 'than', 1]:\n",
     "    print(x)"
@@ -1736,23 +1186,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 179,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "2\n",
-      "3\n",
-      "4\n",
-      "5\n",
-      "6\n",
-      "7\n",
-      "8\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "for x in range(2, 9):\n",
     "  print(x)"
@@ -1769,18 +1205,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 180,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "4\n",
-      "7\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "x, y = [4, 7]\n",
     "print(x)\n",
@@ -1796,21 +1223,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 181,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[('Some', 0), ('set', 1), ('of', 2), ('items', 3)]\n",
-      "0 Some\n",
-      "1 set\n",
-      "2 of\n",
-      "3 items\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "alist = ['Some', 'set', 'of', 'items']\n",
     "blist = list(range(len(alist)))\n",
@@ -1833,17 +1248,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 182,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "34.995996566662235\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "import random\n",
     "n = 0\n",
@@ -1881,17 +1288,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 183,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "0.33573141209899227 0.11271558106998338\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "import random\n",
     "x = random.uniform(0, 1)\n",
@@ -1911,18 +1310,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 184,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n",
-      "[0, 1, 4, 9, 16, 25, 36, 64, 81]\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "v1 = [ x**2 for x in range(10) ]\n",
     "print(v1)\n",
@@ -1959,19 +1349,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 185,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "(22.360679774997898, 500)\n",
-      "37.416573867739416\n",
-      "37.416573867739416\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "def myfunc(x, y, z=0):\n",
     "    r2 = x*x + y*y + z*z\n",
@@ -2002,17 +1382,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 186,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "22.360679774997898 30 60\n"
-     ]
-    }
-   ],
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
    "source": [
     "def myfunc(x, y, z=0, flag=''):\n",
     "    if flag=='L1':\n",
@@ -2041,7 +1413,7 @@
     "Let's say you are given a single string with comma separated elements\n",
     "that represent filenames and ID codes: e.g., `/vols/Data/pytreat/AAC, 165873, /vols/Data/pytreat/AAG, 170285, ...`\n",
     "\n",
-    "Write some code to do the following: \n",
+    "Write some code to do the following:\n",
     " * separate out the filenames and ID codes into separate lists (ID's\n",
     " should be numerical values, not strings) - you may need several steps for this\n",
     " * loop over the two and generate a _string_ that could be used to\n",
@@ -2064,25 +1436,7 @@
    ]
   }
  ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "Python 3",
-   "language": "python",
-   "name": "python3"
-  },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 3
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython3",
-   "version": "3.5.2"
-  }
- },
+ "metadata": {},
  "nbformat": 4,
  "nbformat_minor": 2
 }
diff --git a/getting_started/01_basics.md b/getting_started/01_basics.md
index 9c5928d..ac9372c 100644
--- a/getting_started/01_basics.md
+++ b/getting_started/01_basics.md
@@ -10,7 +10,17 @@ explanations. You can run each block by using _shift + enter_
 (including the text blocks, so you can just move down the document
 with shift + enter).
 
-It is also possible to _change_ the contents of each code block (these pages are completely interactive) so do experiment with the code you see and try some variations!
+It is also possible to _change_ the contents of each code block (these pages
+are completely interactive) so do experiment with the code you see and try
+some variations!
+
+> **Important**: We are exclusively using Python 3 in FSL - as of FSL 6.0.4 we
+> are using Python 3.7. There are some subtle differences between Python 2 and
+> Python 3, but instead of learning about these differences, it is easier to
+> simply forget that Python 2 exists.  When you are googling for Python help,
+> make sure that the pages you find are relevant to Python 3 and *not* Python
+> 2! The official Python docs can be found at https://docs.python.org/3/ (note
+> the _/3/_ at the end!).
 
 ## Contents
 
@@ -104,7 +114,10 @@ print(s3)
 <a class="anchor" id="Format"></a>
 ### Format
 
-More interesting strings can be created using the `format` statement, which is very useful in print statements:
+More interesting strings can be created using the
+[`format`](https://docs.python.org/3/library/string.html#formatstrings)
+statement, which is very useful in print statements:
+
 ```
 x = 1
 y = 'PyTreat'
@@ -113,7 +126,16 @@ print(s)
 print('A name is {} and a number is {}'.format(y, x))
 ```
 
-There are also other options along these lines, but this is the more modern version, although you will see plenty of the other alternatives in "old" code (to python coders this means anything written before last week).
+Python also supports C-style [`%`
+formatting](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting):
+
+```
+x = 1
+y = 'PyTreat'
+s = 'The numerical value is %i and a name is %s' % (x, y)
+print(s)
+print('A name is %s and a number is %i' % (y, x))
+```
 
 <a class="anchor" id="String-manipulation"></a>
 ### String manipulation
@@ -150,7 +172,8 @@ where the `r` before the quote is used to force the regular expression specifica
 
 For more information on matching and substitutions, look up the regular expression module on the web.
 
-Two common and convenient string methods are `strip()` and `split()`.  The first will remove any whitespace at the beginning and end of a string:
+Two common and convenient string methods are `strip()` and `split()`.  The
+first will remove any whitespace at the beginning and end of a string:
 
 ```
 s2 = '   A very    spacy   string       '
@@ -159,6 +182,7 @@ print('*' + s2.strip() + '*')
 ```
 
 With `split()` we can tokenize a string (to turn it into a list of strings) like this:
+
 ```
 print(s.split())
 print(s2.split())
@@ -170,11 +194,27 @@ s4 = '  This is,  as you can    see ,   a very  weirdly spaced and punctuated
 print(s4.split(','))
 ```
 
-There are more powerful ways of dealing with this like csv files/strings, which are covered in later practicals, but even this can get you a long way.
+A neat trick, if you want to change the delimiter in some structured data (e.g.
+replace `,` with `\t`), is to use `split()` in combination with another string
+method, `join()`:
+```
+csvdata = 'some,comma,separated,data'
+tsvdata = '\t'.join(csvdata.split(','))
+tsvdata = tsvdata.replace('comma', 'tab'))
+print('csvdata:', csvdata)
+print('tsvdata:', tsvdata)
+```
+
+
+There are more powerful ways of dealing with this like csv files/strings,
+which are covered in later practicals, but even this can get you a long way.
 
-> Note that strings in python 3 are _unicode_ so can represent Chinese characters, etc, and is therefore very flexible.  However, in general you can just be blissfully ignorant of this fact.
+> Note that strings in python 3 are _unicode_ so can represent Chinese
+> characters, etc, and is therefore very flexible.  However, in general you
+> can just be blissfully ignorant of this fact.
 
-Strings can be converted to integer or floating-point values by using the `int()` and `float()` calls:
+Strings can be converted to integer or floating-point values by using the
+`int()` and `float()` calls:
 
 ```
 sint='23'
@@ -191,8 +231,8 @@ print(float(sint) + float(sfp))
 <a class="anchor" id="Tuples-and-lists"></a>
 ## Tuples and lists
 
-Both tuples and lists are builtin python types and are like vectors, 
-but for numerical vectors and arrays it is much better to use _numpy_
+Both tuples and lists are builtin python types and are like vectors,
+but for numerical vectors and arrays it is much better to use `numpy`
 arrays (or matrices), which are covered in a later tutorial.
 
 A tuple is like a list or a vector, but with less flexibility than a full list (tuples are immutable), however anything can be stored in either a list or tuple, without any consistency being required.  Tuples are defined using round brackets and lists are defined using square brackets. For example:
@@ -222,7 +262,8 @@ a +=  [80]
 print(a)
 ```
 
-> Similar things can be done for tuples, except for the last one: that is, a += (80) as a tuple is immutable so cannot be changed like this. 
+> Similar things can be done for tuples, except for the last one: that is,
+> `a += (80)` as a tuple is immutable so cannot be changed like this.
 
 <a class="anchor" id="Indexing"></a>
 ### Indexing
@@ -242,7 +283,9 @@ print(a[0])
 print(a[2])
 ```
 
-Indices naturally run from 0 to N-1, _but_ negative numbers can be used to reference from the end (circular wrap-around). 
+Indices naturally run from 0 to N-1, _but_ negative numbers can be used to
+reference from the end (circular wrap-around).
+
 ```
 print(a[-1])
 print(a[-6])
@@ -294,7 +337,7 @@ print(a[1:3])    # same as a(2:3) in MATLAB
 > _*Pitfall:*_
 >
 >  Unlike in MATLAB, you cannot use a list as indices instead of an
->  integer or a slice (although these can be done in _numpy_).
+>  integer or a slice (although these can be done in `numpy`).
 
 ```
 b = [3, 4]
@@ -372,7 +415,9 @@ dir(d)
 ```
 
 
-> Note that google is often more helpful!  At least, as long as you find pages relating to the right version of python - we use python 3 for FSL, so check that what you find is appropriate for that.
+> Note that google is often more helpful!  At least, as long as you find pages
+> relating to Python 3 - Python 2 is no longer supported, but there is still
+> lots of information about it on the internet, so be careful!
 
 ---
 
@@ -430,14 +475,20 @@ for k in e:
     print((k, e[k]))
 ```
 
-> Note that in both cases the order is arbitrary. The `sorted` function can be used if you want keys in a sorted order; e.g. `for k in sorted(e):` ...
+> In older versions of Python 3, there was no guarantee of ordering when using dictionaries.
+> However, a of Python 3.7, dictionaries will remember the order in which items are inserted,
+> and the `keys()`, `values()`, and `items()` methods will return elements in that order.
 >
-> There are also [other options](https://docs.python.org/3.5/library/collections.html#collections.OrderedDict) if you want a dictionary with ordering.
+
+> If you want a dictionary with ordering, *and* you want your code to work with
+> Python versions older than 3.7, you can use the
+> [`OrderedDict`](https://docs.python.org/3/library/collections.html#collections.OrderedDict)
+> class.
 
 ---
 
 <a class="anchor" id="Copying-and-references"></a>
-## Copying and references 
+## Copying and references
 
 In python there are immutable types (e.g. numbers) and mutable types (e.g. lists). The main thing to know is that assignment can sometimes create separate copies and sometimes create references (as in C++). In general, the more complicated types are assigned via references. For example:
 ```
@@ -517,7 +568,11 @@ print('b: ', b)
 <a class="anchor" id="Boolean-operators"></a>
 ### Boolean operators
 
-There is a boolean type in python that can be `True` or `False` (note the capitals). Other values can also be used for True or False (e.g., 1 for True; 0 or None or [] or {} or "") although they are not considered 'equal' in the sense that the operator `==` would consider them the same.
+There is a boolean type in python that can be `True` or `False` (note the
+capitals). Other values can also be used for True or False (e.g., `1` for
+`True`; `0` or `None` or `[]` or `{}` or `""` for `False`) although they are
+not considered 'equal' in the sense that the operator `==` would consider them
+the same.
 
 Relevant boolean and comparison operators include: `not`, `and`, `or`, `==` and `!=`
 
@@ -564,7 +619,7 @@ a = []    # just one of many examples
 if not a:
     print('Variable is true, or at least not empty')
 ```
-This can be useful for functions where a variety of possible input types are being dealt with. 
+This can be useful for functions where a variety of possible input types are being dealt with.
 
 ---
 
@@ -722,7 +777,7 @@ You will often see python functions called with these named arguments. In fact,
 Let's say you are given a single string with comma separated elements
 that represent filenames and ID codes: e.g., `/vols/Data/pytreat/AAC, 165873, /vols/Data/pytreat/AAG, 170285, ...`
 
-Write some code to do the following: 
+Write some code to do the following:
  * separate out the filenames and ID codes into separate lists (ID's
  should be numerical values, not strings) - you may need several steps for this
  * loop over the two and generate a _string_ that could be used to
@@ -738,5 +793,3 @@ Write some code to do the following:
 mstr = '/vols/Data/pytreat/AAC, 165873,  /vols/Data/pytreat/AAG,   170285, /vols/Data/pytreat/AAH, 196792, /vols/Data/pytreat/AAK, 212577, /vols/Data/pytreat/AAQ, 385376, /vols/Data/pytreat/AB, 444600, /vols/Data/pytreat/AC6, 454578, /vols/Data/pytreat/V8, 501502,   /vols/Data/pytreat/2YK, 667688, /vols/Data/pytreat/C3PO, 821971'
 
 ```
-
-
diff --git a/getting_started/02_text_io.ipynb b/getting_started/02_text_io.ipynb
index 43ef0db..310b3ad 100644
--- a/getting_started/02_text_io.ipynb
+++ b/getting_started/02_text_io.ipynb
@@ -6,11 +6,16 @@
    "source": [
     "# Text input/output\n",
     "\n",
-    "In this section we will explore how to write and/or retrieve our data from text files.\n",
+    "In this section we will explore how to write and/or retrieve our data from\n",
+    "text files.\n",
     "\n",
-    "Most of the functionality for reading/writing files and manipulating strings is available without any imports. However, you can find some additional functionality in the [`string`](https://docs.python.org/3.6/library/string.html) module.\n",
+    "Most of the functionality for reading/writing files and manipulating strings\n",
+    "is available without any imports. However, you can find some additional\n",
+    "functionality in the\n",
+    "[`string`](https://docs.python.org/3/library/string.html) module.\n",
     "\n",
-    "Most of the string functions are available as methods on string objects. This means that you can use the ipython autocomplete to check for them."
+    "Most of the string functions are available as methods on string objects. This\n",
+    "means that you can use the ipython autocomplete to check for them."
    ]
   },
   {
@@ -28,7 +33,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "empty_string.    # after running the code block above, put your cursor behind the dot and press tab to get a list of methods"
+    "# after running the code block above,\n",
+    "# put your cursor after the dot and\n",
+    "# press tab to get a list of methods\n",
+    "empty_string."
    ]
   },
   {
@@ -49,11 +57,20 @@
     "* [Exercises](#exercises)\n",
     "\n",
     "<a class=\"anchor\" id=\"reading-writing-files\"></a>\n",
+    "\n",
     "## Reading/writing files\n",
-    "The syntax to open a file in python is `with open(<filename>, <mode>) as <file_object>: <block of code>`, where\n",
+    "\n",
+    "\n",
+    "The syntax to open a file in python is `with open(<filename>, <mode>) as\n",
+    "<file_object>: <block of code>`, where\n",
     "* `filename` is a string with the name of the file\n",
-    "* `mode` is one of 'r' (for read-only access), 'w' (for writing a file, this wipes out any existing content), 'a' (for appending to an existing file).\n",
-    "* `file_object` is a variable name which will be used within the `block of code` to access the opened file.\n",
+    "\n",
+    "* `mode` is one of `'r'` (for read-only access), `'w'` (for writing a file,\n",
+    "  this wipes out any existing content), `'a'` (for appending to an existing\n",
+    "  file).\n",
+    "\n",
+    "* `file_object` is a variable name which will be used within the `block of\n",
+    "  code` to access the opened file.\n",
     "\n",
     "For example the following will read all the text in `README.md` and print it."
    ]
@@ -72,10 +89,37 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "> The `with` statement is an advanced python feature, however you will probably only encounter it when opening files. In that context it merely ensures that the file will be properly closed as soon as the program leaves the `with` statement (even if an error is raised within the `with` statement).\n",
-    "\n",
-    "You could also use the `readlines()` method to get a list of all the lines.\n",
+    "> The `with` statement is an advanced python feature, however you will\n",
+    "> probably only encounter it when opening files. In that context it merely\n",
+    "> ensures that the file will be properly closed as soon as the program leaves\n",
+    "> the `with` statement (even if an error is raised within the `with`\n",
+    "> statement).\n",
     "\n",
+    "You could also use the `readlines()` method to get a list of all the lines, or\n",
+    "simply \"loop over\" the file object to get the lines one by one:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "with open('README.md', 'r') as readme_file:\n",
+    "    print('First five lines...')\n",
+    "    for i, line in enumerate(readme_file):\n",
+    "        # each line is returned with its\n",
+    "        # newline character still intact,\n",
+    "        # so we use rstrip() to remove it.\n",
+    "        print('{}: {}'.format(i, line.rstrip()))\n",
+    "        if i == 4:\n",
+    "            break"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
     "A very similar syntax is used to write files:"
    ]
   },
@@ -94,7 +138,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Note that no new line characters get added automatically. We can investigate the resulting file using"
+    "Note that no new line characters get added automatically. We can investigate\n",
+    "the resulting file using"
    ]
   },
   {
@@ -110,7 +155,12 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "> Any lines starting with `!` will be interpreted as shell commands by ipython. It is great when playing around in the ipython notebook or in the ipython terminal, however it is an ipython-only feature and hence is not available when writing python scripts. How to call shell commands from python will be discussed in the `scripts` practical.\n",
+    "> In Jupyter notebook, (and in `ipython`/`fslipython`), any lines starting\n",
+    "> with `!` will be interpreted as shell commands. It is great when playing\n",
+    "> around in a Jupyter notebook or in the `ipython` terminal, however it is an\n",
+    "> ipython-only feature and hence is not available when writing python\n",
+    "> scripts. How to call shell commands from python will be discussed in the\n",
+    "> `scripts` practical.\n",
     "\n",
     "If we want to add to the existing file we can open it in the append mode:"
    ]
@@ -130,14 +180,18 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Below we will discuss how we can convert python objects to strings to store in these files and how to extract those python objects from strings again.\n",
+    "Below we will discuss how we can convert python objects to strings to store in\n",
+    "these files and how to extract those python objects from strings again.\n",
     "\n",
     "<a class=\"anchor\" id=\"creating-new-strings\"></a>\n",
     "## Creating new strings\n",
     "\n",
     "<a class=\"anchor\" id=\"string-syntax\"></a>\n",
     "### String syntax\n",
-    "Single-line strings can be created in python using either single or double quotes"
+    "\n",
+    "\n",
+    "Single-line strings can be created in python using either single or double\n",
+    "quotes:"
    ]
   },
   {
@@ -155,7 +209,10 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The main rationale for choosing between single or double quotes, is whether the string itself will contain any quotes. You can include a single quote in a string surrounded by single quotes by escaping it with the `\\` character, however in such a case it would be more convenient to use double quotes:"
+    "The main rationale for choosing between single or double quotes, is whether\n",
+    "the string itself will contain any quotes. You can include a single quote in a\n",
+    "string surrounded by single quotes by escaping it with the `\\` character,\n",
+    "however in such a case it would be more convenient to use double quotes:"
    ]
   },
   {
@@ -245,9 +302,19 @@
    "source": [
     "<a class=\"anchor\" id=\"unicode-versus-bytes\"></a>\n",
     "#### unicode versus bytes\n",
-    "To encourage the spread of python around the world, python 3 switched to using unicode as the default for strings and code (which is one of the main reasons for the incompatibility between python 2 and 3).\n",
-    "This means that each element in a string is a unicode character (using [UTF-8 encoding](https://docs.python.org/3/howto/unicode.html)), which can consist of one or more bytes.\n",
-    "The advantage is that any unicode characters can now be used in strings or in the code itself:"
+    "\n",
+    "> **Note**: You can safely skip this section if you do not have any plans to\n",
+    "> work with binary files or non-English text in Python, and you do  not want\n",
+    "> to know how to insert poop emojis into your code.\n",
+    "\n",
+    "\n",
+    "To encourage the spread of python around the world, python 3 switched to using\n",
+    "unicode as the default for strings and code (which is one of the main reasons\n",
+    "for the incompatibility between python 2 and 3).  This means that each element\n",
+    "in a string is a unicode character (using [UTF-8\n",
+    "encoding](https://docs.python.org/3/howto/unicode.html)), which can consist of\n",
+    "one or more bytes.  The advantage is that any unicode characters can now be\n",
+    "used in strings or in the code itself:"
    ]
   },
   {
@@ -264,7 +331,10 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "In python 2 each element in a string was a single byte rather than a potentially multi-byte character. You can convert back to interpreting your sequence as a unicode string or a byte array using:\n",
+    "In python 2 each element in a string was a single byte rather than a\n",
+    "potentially multi-byte character. You can convert back to interpreting your\n",
+    "sequence as a unicode string or a byte array using:\n",
+    "\n",
     "* `encode()` called on a string converts it into a bytes array (`bytes` object)\n",
     "* `decode()` called on a `bytes` array converts it into a unicode string."
    ]
@@ -283,7 +353,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "These byte arrays can be created directly be prepending the quotes enclosing the string with a `b`, which tells python 3 to interpret the following as a byte array:"
+    "These byte arrays can be created directly by prepending the quotes enclosing\n",
+    "the string with a `b`, which tells python 3 to interpret the following as a\n",
+    "byte array:"
    ]
   },
   {
@@ -300,9 +372,14 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Especially in code dealing with strings (e.g., reading/writing of files) many of the errors arising of running python 2 code in python 3 arise from the mixing of unicode strings with byte arrays. Decoding and/or encoding some of these objects can often fix these issues.\n",
+    "Especially in code dealing with strings (e.g., reading/writing of files) many\n",
+    "of the errors arising of running python 2 code in python 3 arise from the\n",
+    "mixing of unicode strings with byte arrays. Decoding and/or encoding some of\n",
+    "these objects can often fix these issues.\n",
     "\n",
-    "By default any file opened in python will be interpreted as unicode. If you want to treat a file as raw bytes, you have to include a 'b' in the `mode` when calling the `open()` function:"
+    "By default any file opened in python will be interpreted as unicode. If you\n",
+    "want to treat a file as raw bytes, you have to include a 'b' in the `mode`\n",
+    "when calling the `open()` function:"
    ]
   },
   {
@@ -320,14 +397,21 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "> We use the `expandvars()` function here to insert the FSLDIR environmental variable into our string. This function will be presented in the file management practical.\n",
+    "> We use the `expandvars()` function here to insert the FSLDIR environmental\n",
+    "> variable into our string. This function will be presented in the file\n",
+    "> management practical.\n",
+    "\n",
     "\n",
     "<a class=\"anchor\" id=\"converting-objects-into-strings\"></a>\n",
     "### converting objects into strings\n",
-    "There are two functions to convert python objects into strings, `repr()` and `str()`.\n",
-    "All other functions that rely on string-representations of python objects will use one of these two (for example the `print()` function will call `str()` on the object).\n",
     "\n",
-    "The goal of the `str()` function is to be readable, while the goal of `repr()` is to be unambiguous. Compare"
+    "There are two functions to convert python objects into strings, `repr()` and\n",
+    "`str()`.  All other functions that rely on string-representations of python\n",
+    "objects will use one of these two (for example the `print()` function will\n",
+    "call `str()` on the object).\n",
+    "\n",
+    "The goal of the `str()` function is to be readable, while the goal of `repr()`\n",
+    "is to be unambiguous. Compare"
    ]
   },
   {
@@ -445,10 +529,10 @@
     "<a class=\"anchor\" id=\"string-formatting\"></a>\n",
     "### String formatting\n",
     "Using the techniques in [Combining strings](#combining-strings) we can build simple strings. For longer strings it is often useful to first write a template strings with some placeholders, where variables are later inserted. Built into python are currently 4 different ways of doing this (with many packages providing similar capabilities):\n",
-    "* the recommended [new-style formatting](https://docs.python.org/3.6/library/string.html#format-string-syntax).\n",
+    "* the recommended [new-style formatting](https://docs.python.org/3/library/string.html#format-string-syntax).\n",
     "* printf-like [old-style formatting](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)\n",
-    "* [formatted string literals](https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings) (these are only available in python 3.6+)\n",
-    "* bash-like [template-strings](https://docs.python.org/3.6/library/string.html#template-strings)\n",
+    "* [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) (these are only available in python 3.6+)\n",
+    "* bash-like [template-strings](https://docs.python.org/3/library/string.html#template-strings)\n",
     "\n",
     "Here we provide a single example using the first three methods, so you can recognize them in the future.\n",
     "\n",
@@ -520,6 +604,14 @@
     "\n",
     "<a class=\"anchor\" id=\"extracting-information-from-strings\"></a>\n",
     "## Extracting information from strings\n",
+    "\n",
+    "The techniques shown in this section are useful if you are loading data from a\n",
+    "small text file or user input, or parsing a small amount of output from\n",
+    "e.g. `fslstats`. However, if you are working with large structured text data\n",
+    "(e.g. a big `csv` file), you should use the I/O capabilities of `numpy` or\n",
+    "`pandas` instead of doing things manually - this is covered in separate\n",
+    "practcals.\n",
+    "\n",
     "<a class=\"anchor\" id=\"splitting-strings\"></a>\n",
     "### Splitting strings\n",
     "The simplest way to extract a sub-string is to use slicing"
@@ -598,9 +690,14 @@
    "source": [
     "> We use the syntax `[<expr> for <element> in <sequence>]` here which applies the `expr` to each `element` in the `sequence` and returns the resulting list. This is a list comprehension - a convenient form in python to create a new list from the old one.\n",
     "\n",
+    "\n",
     "<a class=\"anchor\" id=\"converting-strings-to-numbers\"></a>\n",
     "### Converting strings to numbers\n",
-    "Once you have extracted a number from a string, you can convert it into an actual integer or float by calling respectively `int()` or `float()` on it. `float()` understands a wide variety of different ways to write numbers:"
+    "\n",
+    "\n",
+    "Once you have extracted a number from a string, you can convert it into an\n",
+    "actual integer or float by calling respectively `int()` or `float()` on\n",
+    "it. `float()` understands a wide variety of different ways to write numbers:"
    ]
   },
   {
diff --git a/getting_started/02_text_io.md b/getting_started/02_text_io.md
index afab42f..42e93b0 100644
--- a/getting_started/02_text_io.md
+++ b/getting_started/02_text_io.md
@@ -1,16 +1,25 @@
 # Text input/output
 
-In this section we will explore how to write and/or retrieve our data from text files.
+In this section we will explore how to write and/or retrieve our data from
+text files.
 
-Most of the functionality for reading/writing files and manipulating strings is available without any imports. However, you can find some additional functionality in the [`string`](https://docs.python.org/3.6/library/string.html) module.
+Most of the functionality for reading/writing files and manipulating strings
+is available without any imports. However, you can find some additional
+functionality in the
+[`string`](https://docs.python.org/3/library/string.html) module.
+
+Most of the string functions are available as methods on string objects. This
+means that you can use the ipython autocomplete to check for them.
 
-Most of the string functions are available as methods on string objects. This means that you can use the ipython autocomplete to check for them.
 ```
 empty_string = ''
 ```
 
 ```
-empty_string.    # after running the code block above, put your cursor behind the dot and press tab to get a list of methods
+# after running the code block above,
+# put your cursor after the dot and
+# press tab to get a list of methods
+empty_string.
 ```
 
 * [Reading/writing files](#reading-writing-files)
@@ -27,20 +36,47 @@ empty_string.    # after running the code block above, put your cursor behind th
 * [Exercises](#exercises)
 
 <a class="anchor" id="reading-writing-files"></a>
+
 ## Reading/writing files
-The syntax to open a file in python is `with open(<filename>, <mode>) as <file_object>: <block of code>`, where
+
+
+The syntax to open a file in python is `with open(<filename>, <mode>) as
+<file_object>: <block of code>`, where
 * `filename` is a string with the name of the file
-* `mode` is one of 'r' (for read-only access), 'w' (for writing a file, this wipes out any existing content), 'a' (for appending to an existing file).
-* `file_object` is a variable name which will be used within the `block of code` to access the opened file.
+
+* `mode` is one of `'r'` (for read-only access), `'w'` (for writing a file,
+  this wipes out any existing content), `'a'` (for appending to an existing
+  file).
+
+* `file_object` is a variable name which will be used within the `block of
+  code` to access the opened file.
 
 For example the following will read all the text in `README.md` and print it.
 ```
 with open('README.md', 'r') as readme_file:
     print(readme_file.read())
 ```
-> The `with` statement is an advanced python feature, however you will probably only encounter it when opening files. In that context it merely ensures that the file will be properly closed as soon as the program leaves the `with` statement (even if an error is raised within the `with` statement).
 
-You could also use the `readlines()` method to get a list of all the lines.
+> The `with` statement is an advanced python feature, however you will
+> probably only encounter it when opening files. In that context it merely
+> ensures that the file will be properly closed as soon as the program leaves
+> the `with` statement (even if an error is raised within the `with`
+> statement).
+
+You could also use the `readlines()` method to get a list of all the lines, or
+simply "loop over" the file object to get the lines one by one:
+
+```
+with open('README.md', 'r') as readme_file:
+    print('First five lines...')
+    for i, line in enumerate(readme_file):
+        # each line is returned with its
+        # newline character still intact,
+        # so we use rstrip() to remove it.
+        print('{}: {}'.format(i, line.rstrip()))
+        if i == 4:
+            break
+```
 
 A very similar syntax is used to write files:
 ```
@@ -48,11 +84,21 @@ with open('02_text_io/my_file', 'w') as my_file:
     my_file.write('This is my first line\n')
     my_file.writelines(['Second line\n', 'and the third\n'])
 ```
-Note that no new line characters get added automatically. We can investigate the resulting file using
+
+Note that no new line characters get added automatically. We can investigate
+the resulting file using
+
 ```
 !cat 02_text_io/my_file
 ```
-> Any lines starting with `!` will be interpreted as shell commands by ipython. It is great when playing around in the ipython notebook or in the ipython terminal, however it is an ipython-only feature and hence is not available when writing python scripts. How to call shell commands from python will be discussed in the `scripts` practical.
+
+
+> In Jupyter notebook, (and in `ipython`/`fslipython`), any lines starting
+> with `!` will be interpreted as shell commands. It is great when playing
+> around in a Jupyter notebook or in the `ipython` terminal, however it is an
+> ipython-only feature and hence is not available when writing python
+> scripts. How to call shell commands from python will be discussed in the
+> `scripts` practical.
 
 If we want to add to the existing file we can open it in the append mode:
 ```
@@ -61,21 +107,30 @@ with open('02_text_io/my_file', 'a') as my_file:
 !cat 02_text_io/my_file
 ```
 
-Below we will discuss how we can convert python objects to strings to store in these files and how to extract those python objects from strings again.
+Below we will discuss how we can convert python objects to strings to store in
+these files and how to extract those python objects from strings again.
 
 <a class="anchor" id="creating-new-strings"></a>
 ## Creating new strings
 
 <a class="anchor" id="string-syntax"></a>
 ### String syntax
-Single-line strings can be created in python using either single or double quotes
+
+
+Single-line strings can be created in python using either single or double
+quotes:
+
 ```
 a_string = 'To be or not to be'
 same_string = "To be or not to be"
 print(a_string == same_string)
 ```
 
-The main rationale for choosing between single or double quotes, is whether the string itself will contain any quotes. You can include a single quote in a string surrounded by single quotes by escaping it with the `\` character, however in such a case it would be more convenient to use double quotes:
+The main rationale for choosing between single or double quotes, is whether
+the string itself will contain any quotes. You can include a single quote in a
+string surrounded by single quotes by escaping it with the `\` character,
+however in such a case it would be more convenient to use double quotes:
+
 ```
 a_string = "That's the question"
 same_string = 'That\'s the question'
@@ -110,45 +165,78 @@ print("The 'c' and 'd' got concatenated, because we forgot the comma:", my_list_
 
 <a class="anchor" id="unicode-versus-bytes"></a>
 #### unicode versus bytes
-To encourage the spread of python around the world, python 3 switched to using unicode as the default for strings and code (which is one of the main reasons for the incompatibility between python 2 and 3).
-This means that each element in a string is a unicode character (using [UTF-8 encoding](https://docs.python.org/3/howto/unicode.html)), which can consist of one or more bytes.
-The advantage is that any unicode characters can now be used in strings or in the code itself:
+
+> **Note**: You can safely skip this section if you do not have any plans to
+> work with binary files or non-English text in Python, and you do  not want
+> to know how to insert poop emojis into your code.
+
+
+To encourage the spread of python around the world, python 3 switched to using
+unicode as the default for strings and code (which is one of the main reasons
+for the incompatibility between python 2 and 3).  This means that each element
+in a string is a unicode character (using [UTF-8
+encoding](https://docs.python.org/3/howto/unicode.html)), which can consist of
+one or more bytes.  The advantage is that any unicode characters can now be
+used in strings or in the code itself:
+
 ```
 Δ = "café"
 print(Δ)
 ```
 
 
-In python 2 each element in a string was a single byte rather than a potentially multi-byte character. You can convert back to interpreting your sequence as a unicode string or a byte array using:
+In python 2 each element in a string was a single byte rather than a
+potentially multi-byte character. You can convert back to interpreting your
+sequence as a unicode string or a byte array using:
+
 * `encode()` called on a string converts it into a bytes array (`bytes` object)
 * `decode()` called on a `bytes` array converts it into a unicode string.
+
 ```
 delta = "Δ"
 print('The character', delta, 'consists of the following 2 bytes', delta.encode())
 ```
 
-These byte arrays can be created directly be prepending the quotes enclosing the string with a `b`, which tells python 3 to interpret the following as a byte array:
+These byte arrays can be created directly by prepending the quotes enclosing
+the string with a `b`, which tells python 3 to interpret the following as a
+byte array:
+
 ```
 a_byte_array = b'\xce\xa9'
 print('The two bytes ', a_byte_array, ' become single unicode character (', a_byte_array.decode(), ') with UTF-8 encoding')
 ```
 
-Especially in code dealing with strings (e.g., reading/writing of files) many of the errors arising of running python 2 code in python 3 arise from the mixing of unicode strings with byte arrays. Decoding and/or encoding some of these objects can often fix these issues.
+Especially in code dealing with strings (e.g., reading/writing of files) many
+of the errors arising of running python 2 code in python 3 arise from the
+mixing of unicode strings with byte arrays. Decoding and/or encoding some of
+these objects can often fix these issues.
+
+By default any file opened in python will be interpreted as unicode. If you
+want to treat a file as raw bytes, you have to include a 'b' in the `mode`
+when calling the `open()` function:
 
-By default any file opened in python will be interpreted as unicode. If you want to treat a file as raw bytes, you have to include a 'b' in the `mode` when calling the `open()` function:
 ```
 import os.path as op
 with open(op.expandvars('${FSLDIR}/data/standard/MNI152_T1_1mm.nii.gz'), 'rb') as gzipped_nifti:
     print('First few bytes of gzipped NIFTI file:', gzipped_nifti.read(10))
 ```
-> We use the `expandvars()` function here to insert the FSLDIR environmental variable into our string. This function will be presented in the file management practical.
+
+> We use the `expandvars()` function here to insert the FSLDIR environmental
+> variable into our string. This function will be presented in the file
+> management practical.
+
 
 <a class="anchor" id="converting-objects-into-strings"></a>
 ### converting objects into strings
-There are two functions to convert python objects into strings, `repr()` and `str()`.
-All other functions that rely on string-representations of python objects will use one of these two (for example the `print()` function will call `str()` on the object).
 
-The goal of the `str()` function is to be readable, while the goal of `repr()` is to be unambiguous. Compare
+There are two functions to convert python objects into strings, `repr()` and
+`str()`.  All other functions that rely on string-representations of python
+objects will use one of these two (for example the `print()` function will
+call `str()` on the object).
+
+The goal of the `str()` function is to be readable, while the goal of `repr()`
+is to be unambiguous. Compare
+
 ```
 print(str("3"))
 print(str(3))
@@ -198,10 +286,10 @@ print(full_string)
 <a class="anchor" id="string-formatting"></a>
 ### String formatting
 Using the techniques in [Combining strings](#combining-strings) we can build simple strings. For longer strings it is often useful to first write a template strings with some placeholders, where variables are later inserted. Built into python are currently 4 different ways of doing this (with many packages providing similar capabilities):
-* the recommended [new-style formatting](https://docs.python.org/3.6/library/string.html#format-string-syntax).
+* the recommended [new-style formatting](https://docs.python.org/3/library/string.html#format-string-syntax).
 * printf-like [old-style formatting](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)
-* [formatted string literals](https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings) (these are only available in python 3.6+)
-* bash-like [template-strings](https://docs.python.org/3.6/library/string.html#template-strings)
+* [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) (these are only available in python 3.6+)
+* bash-like [template-strings](https://docs.python.org/3/library/string.html#template-strings)
 
 Here we provide a single example using the first three methods, so you can recognize them in the future.
 
@@ -238,6 +326,14 @@ This code block will fail in fslpython, since it uses python 3.5.
 
 <a class="anchor" id="extracting-information-from-strings"></a>
 ## Extracting information from strings
+
+The techniques shown in this section are useful if you are loading data from a
+small text file or user input, or parsing a small amount of output from
+e.g. `fslstats`. However, if you are working with large structured text data
+(e.g. a big `csv` file), you should use the I/O capabilities of `numpy` or
+`pandas` instead of doing things manually - this is covered in separate
+practcals.
+
 <a class="anchor" id="splitting-strings"></a>
 ### Splitting strings
 The simplest way to extract a sub-string is to use slicing
@@ -271,9 +367,15 @@ print(list_without_whitespace)
 ```
 > We use the syntax `[<expr> for <element> in <sequence>]` here which applies the `expr` to each `element` in the `sequence` and returns the resulting list. This is a list comprehension - a convenient form in python to create a new list from the old one.
 
+
 <a class="anchor" id="converting-strings-to-numbers"></a>
 ### Converting strings to numbers
-Once you have extracted a number from a string, you can convert it into an actual integer or float by calling respectively `int()` or `float()` on it. `float()` understands a wide variety of different ways to write numbers:
+
+
+Once you have extracted a number from a string, you can convert it into an
+actual integer or float by calling respectively `int()` or `float()` on
+it. `float()` understands a wide variety of different ways to write numbers:
+
 ```
 print(int("3"))
 print(float("3"))
@@ -282,6 +384,7 @@ print(float("3.213e5"))
 print(float("3.213E-25"))
 ```
 
+
 <a class="anchor" id="regular-expressions"></a>
 ### Regular expressions
 Regular expressions are used for looking for specific patterns in a longer string. This can be used to extract specific information from a well-formatted string or to modify a string. In python regular expressions are available in the [re](https://docs.python.org/3/library/re.html#re-syntax) module.
diff --git a/getting_started/03_file_management.ipynb b/getting_started/03_file_management.ipynb
index 2dd68d7..a677f0d 100644
--- a/getting_started/03_file_management.ipynb
+++ b/getting_started/03_file_management.ipynb
@@ -15,11 +15,11 @@
     "across the following modules:\n",
     "\n",
     "\n",
-    " - [`os`](https://docs.python.org/3.5/library/os.html)\n",
-    " - [`shutil`](https://docs.python.org/3.5/library/shutil.html)\n",
-    " - [`os.path`](https://docs.python.org/3.5/library/os.path.html)\n",
-    " - [`glob`](https://docs.python.org/3.5/library/glob.html)\n",
-    " - [`fnmatch`](https://docs.python.org/3.5/library/fnmatch.html)\n",
+    " - [`os`](https://docs.python.org/3/library/os.html)\n",
+    " - [`shutil`](https://docs.python.org/3/library/shutil.html)\n",
+    " - [`os.path`](https://docs.python.org/3/library/os.path.html)\n",
+    " - [`glob`](https://docs.python.org/3/library/glob.html)\n",
+    " - [`fnmatch`](https://docs.python.org/3/library/fnmatch.html)\n",
     "\n",
     "\n",
     "The `os` and `shutil` modules have functions allowing you to manage _files and\n",
@@ -28,7 +28,7 @@
     "\n",
     "\n",
     "> Another standard library -\n",
-    "> [`pathlib`](https://docs.python.org/3.5/library/pathlib.html) - was added in\n",
+    "> [`pathlib`](https://docs.python.org/3/library/pathlib.html) - was added in\n",
     "> Python 3.4, and provides an object-oriented interface to path management. We\n",
     "> aren't going to cover `pathlib` here, but feel free to take a look at it if\n",
     "> you are into that sort of thing.\n",
@@ -296,7 +296,7 @@
     "> Note that `os.walk` does not guarantee a specific ordering in the lists of\n",
     "> files and sub-directories that it returns. However, you can force an\n",
     "> ordering quite easily - see its\n",
-    "> [documentation](https://docs.python.org/3.5/library/os.html#os.walk) for\n",
+    "> [documentation](https://docs.python.org/3/library/os.html#os.walk) for\n",
     "> more details.\n",
     "\n",
     "\n",
@@ -501,7 +501,7 @@
     "> ```\n",
     ">\n",
     "> Take a look at the [official Python\n",
-    "> tutorial](https://docs.python.org/3.5/tutorial/controlflow.html#defining-functions)\n",
+    "> tutorial](https://docs.python.org/3/tutorial/controlflow.html#defining-functions)\n",
     "> for more details on defining your own functions.\n",
     "\n",
     "\n",
@@ -608,7 +608,11 @@
     "> Correct handling of them is an open problem in Computer Science, and is\n",
     "> considered by many to be unsolvable.  For `imglob`, `imcp`, and `immv`-like\n",
     "> functionality, check out the `fsl.utils.path` and `fsl.utils.imcp` modules,\n",
-    "> part of the [`fslpy` project](https://pypi.python.org/pypi/fslpy).\n",
+    "> part of the [`fslpy`\n",
+    "> project](https://users.fmrib.ox.ac.uk/~paulmc/fsleyes/fslpy/latest/). If you\n",
+    "> are using `fslpython`, then you already have access to all of the functions\n",
+    "> in `fslpy`.\n",
+    "\n",
     "\n",
     "\n",
     "<a class=\"anchor\" id=\"absolute-and-relative-paths\"></a>\n",
@@ -751,7 +755,7 @@
    "source": [
     "Now that we have this function, we can sort the directories in one line of\n",
     "code, via the built-in\n",
-    "[`sorted`](https://docs.python.org/3.5/library/functions.html#sorted)\n",
+    "[`sorted`](https://docs.python.org/3/library/functions.html#sorted)\n",
     "function.  The directories will be sorted according to the `key` function that\n",
     "we specify, which provides a mapping from each directory to a sortable\n",
     "&quot;key&quot;:"
@@ -812,10 +816,10 @@
     "\n",
     "Note that the syntax used by `glob` and `fnmatch` is similar, but __not__\n",
     "identical to the syntax that you are used to from `bash`. Refer to the\n",
-    "[`fnmatch` module](https://docs.python.org/3.5/library/fnmatch.html)\n",
+    "[`fnmatch` module](https://docs.python.org/3/library/fnmatch.html)\n",
     "documentation for details. If you need more complicated pattern matching, you\n",
     "can use regular expressions, available via the [`re`\n",
-    "module](https://docs.python.org/3.5/library/re.html).\n",
+    "module](https://docs.python.org/3/library/re.html).\n",
     "\n",
     "\n",
     "For example, let's retrieve all images that are in our data set:"
@@ -1080,7 +1084,7 @@
     "\n",
     "> There are many different types of exceptions in Python - a list of all the\n",
     "> built-in ones can be found\n",
-    "> [here](https://docs.python.org/3.5/library/exceptions.html). It is also easy\n",
+    "> [here](https://docs.python.org/3/library/exceptions.html). It is also easy\n",
     "> to define your own exceptions by creating a sub-class of `Exception` (beyond\n",
     "> the scope of this practical).\n",
     "\n",
@@ -1346,7 +1350,7 @@
     "\n",
     "\n",
     "You can read more about handling exceptions in Python\n",
-    "[here](https://docs.python.org/3.5/tutorial/errors.html).\n",
+    "[here](https://docs.python.org/3/tutorial/errors.html).\n",
     "\n",
     "\n",
     "### Raising exceptions\n",
diff --git a/getting_started/03_file_management.md b/getting_started/03_file_management.md
index 63b4798..0c4979a 100644
--- a/getting_started/03_file_management.md
+++ b/getting_started/03_file_management.md
@@ -9,11 +9,11 @@ Most of Python's built-in functionality for managing files and paths is spread
 across the following modules:
 
 
- - [`os`](https://docs.python.org/3.5/library/os.html)
- - [`shutil`](https://docs.python.org/3.5/library/shutil.html)
- - [`os.path`](https://docs.python.org/3.5/library/os.path.html)
- - [`glob`](https://docs.python.org/3.5/library/glob.html)
- - [`fnmatch`](https://docs.python.org/3.5/library/fnmatch.html)
+ - [`os`](https://docs.python.org/3/library/os.html)
+ - [`shutil`](https://docs.python.org/3/library/shutil.html)
+ - [`os.path`](https://docs.python.org/3/library/os.path.html)
+ - [`glob`](https://docs.python.org/3/library/glob.html)
+ - [`fnmatch`](https://docs.python.org/3/library/fnmatch.html)
 
 
 The `os` and `shutil` modules have functions allowing you to manage _files and
@@ -22,7 +22,7 @@ managing file and directory _paths_.
 
 
 > Another standard library -
-> [`pathlib`](https://docs.python.org/3.5/library/pathlib.html) - was added in
+> [`pathlib`](https://docs.python.org/3/library/pathlib.html) - was added in
 > Python 3.4, and provides an object-oriented interface to path management. We
 > aren't going to cover `pathlib` here, but feel free to take a look at it if
 > you are into that sort of thing.
@@ -226,7 +226,7 @@ for root, dirs, files in os.walk('raw_mri_data'):
 > Note that `os.walk` does not guarantee a specific ordering in the lists of
 > files and sub-directories that it returns. However, you can force an
 > ordering quite easily - see its
-> [documentation](https://docs.python.org/3.5/library/os.html#os.walk) for
+> [documentation](https://docs.python.org/3/library/os.html#os.walk) for
 > more details.
 
 
@@ -391,7 +391,7 @@ def whatisit(path, existonly=False):
 > ```
 >
 > Take a look at the [official Python
-> tutorial](https://docs.python.org/3.5/tutorial/controlflow.html#defining-functions)
+> tutorial](https://docs.python.org/3/tutorial/controlflow.html#defining-functions)
 > for more details on defining your own functions.
 
 
@@ -474,7 +474,11 @@ print('Suffix: {}'.format(suffix))
 > Correct handling of them is an open problem in Computer Science, and is
 > considered by many to be unsolvable.  For `imglob`, `imcp`, and `immv`-like
 > functionality, check out the `fsl.utils.path` and `fsl.utils.imcp` modules,
-> part of the [`fslpy` project](https://pypi.python.org/pypi/fslpy).
+> part of the [`fslpy`
+> project](https://users.fmrib.ox.ac.uk/~paulmc/fsleyes/fslpy/latest/). If you
+> are using `fslpython`, then you already have access to all of the functions
+> in `fslpy`.
+
 
 
 <a class="anchor" id="absolute-and-relative-paths"></a>
@@ -577,7 +581,7 @@ print(get_subject_id('raw_mri_data/subj_9'))
 
 Now that we have this function, we can sort the directories in one line of
 code, via the built-in
-[`sorted`](https://docs.python.org/3.5/library/functions.html#sorted)
+[`sorted`](https://docs.python.org/3/library/functions.html#sorted)
 function.  The directories will be sorted according to the `key` function that
 we specify, which provides a mapping from each directory to a sortable
 &quot;key&quot;:
@@ -622,10 +626,10 @@ pattern matching logic.
 
 Note that the syntax used by `glob` and `fnmatch` is similar, but __not__
 identical to the syntax that you are used to from `bash`. Refer to the
-[`fnmatch` module](https://docs.python.org/3.5/library/fnmatch.html)
+[`fnmatch` module](https://docs.python.org/3/library/fnmatch.html)
 documentation for details. If you need more complicated pattern matching, you
 can use regular expressions, available via the [`re`
-module](https://docs.python.org/3.5/library/re.html).
+module](https://docs.python.org/3/library/re.html).
 
 
 For example, let's retrieve all images that are in our data set:
@@ -834,7 +838,7 @@ errors. For example, when you type CTRL+C into a running Python program, a
 
 > There are many different types of exceptions in Python - a list of all the
 > built-in ones can be found
-> [here](https://docs.python.org/3.5/library/exceptions.html). It is also easy
+> [here](https://docs.python.org/3/library/exceptions.html). It is also easy
 > to define your own exceptions by creating a sub-class of `Exception` (beyond
 > the scope of this practical).
 
@@ -1037,7 +1041,7 @@ finally:
 
 
 You can read more about handling exceptions in Python
-[here](https://docs.python.org/3.5/tutorial/errors.html).
+[here](https://docs.python.org/3/tutorial/errors.html).
 
 
 ### Raising exceptions
diff --git a/getting_started/04_numpy.ipynb b/getting_started/04_numpy.ipynb
index bccf81a..ce634dd 100644
--- a/getting_started/04_numpy.ipynb
+++ b/getting_started/04_numpy.ipynb
@@ -19,11 +19,6 @@
     "alternative to Matlab as a scientific computing platform.\n",
     "\n",
     "\n",
-    "The `fslpython` environment currently includes [Numpy\n",
-    "1.11.1](https://docs.scipy.org/doc/numpy-1.11.0/index.html), which is a little\n",
-    "out of date, but we will update it for the next release of FSL.\n",
-    "\n",
-    "\n",
     "## Contents\n",
     "\n",
     "\n",
@@ -102,7 +97,7 @@
    "source": [
     "For simple tasks, you could stick with processing your data using python\n",
     "lists, and the built-in\n",
-    "[`math`](https://docs.python.org/3.5/library/math.html) library. And this\n",
+    "[`math`](https://docs.python.org/3/library/math.html) library. And this\n",
     "might be tempting, because it does look quite a lot like what you might type\n",
     "into Matlab.\n",
     "\n",
@@ -444,8 +439,8 @@
     "\n",
     "\n",
     "> <sup>2</sup> Python, being an object-oriented language, distinguishes\n",
-    "> between _functions_ and _methods_. Hopefully we all know what a function is\n",
-    "> - a _method_ is simply the term used to refer to a function that is\n",
+    "> between _functions_ and _methods_. Hopefully we all know what a function\n",
+    "> is - a _method_ is simply the term used to refer to a function that is\n",
     "> associated with a specific object. Similarly, the term _attribute_ is used\n",
     "> to refer to some piece of information that is attached to an object, such as\n",
     "> `z.shape`, or `z.dtype`.\n",
@@ -819,7 +814,7 @@
     "### Broadcasting\n",
     "\n",
     "\n",
-    "One of the coolest features of Numpy is _broadcasting_<sup>3</sup>.\n",
+    "One of the coolest features of Numpy is *broadcasting*<sup>3</sup>.\n",
     "Broadcasting allows you to perform element-wise operations on arrays which\n",
     "have a different shape. For each axis in the two arrays, Numpy will implicitly\n",
     "expand the shape of the smaller axis to match the shape of the larger one. You\n",
@@ -1272,9 +1267,9 @@
     "\n",
     "> <sup>4</sup> Even though these are FLIRT transforms, this is just a toy\n",
     "> example.  Look\n",
-    "> [here](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FLIRT/FAQ#What_is_the_format_of_the_matrix_used_by_FLIRT.2C_and_how_does_it_relate_to_the_transformation_parameters.3F)\n",
+    "> [here](https://users.fmrib.ox.ac.uk/~paulmc/fsleyes/fslpy/latest/fsl.transform.flirt.html)\n",
     "> and\n",
-    "> [here](https://git.fmrib.ox.ac.uk/fsl/fslpy/blob/1.6.2/fsl/utils/transform.py#L537)\n",
+    "> [here](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FLIRT/FAQ#What_is_the_format_of_the_matrix_used_by_FLIRT.2C_and_how_does_it_relate_to_the_transformation_parameters.3F)\n",
     "> if you actually need to work with FLIRT transforms.\n",
     "\n",
     "\n",
diff --git a/getting_started/04_numpy.md b/getting_started/04_numpy.md
index 3a28cc8..db6fcf9 100644
--- a/getting_started/04_numpy.md
+++ b/getting_started/04_numpy.md
@@ -13,11 +13,6 @@ important Python libraries, and it (along with its partners
 alternative to Matlab as a scientific computing platform.
 
 
-The `fslpython` environment currently includes [Numpy
-1.11.1](https://docs.scipy.org/doc/numpy-1.11.0/index.html), which is a little
-out of date, but we will update it for the next release of FSL.
-
-
 ## Contents
 
 
@@ -80,7 +75,7 @@ xyz_coords = [[-11.4,   1.0,  22.6],
 
 For simple tasks, you could stick with processing your data using python
 lists, and the built-in
-[`math`](https://docs.python.org/3.5/library/math.html) library. And this
+[`math`](https://docs.python.org/3/library/math.html) library. And this
 might be tempting, because it does look quite a lot like what you might type
 into Matlab.
 
@@ -334,8 +329,8 @@ view of the array.
 
 
 > <sup>2</sup> Python, being an object-oriented language, distinguishes
-> between _functions_ and _methods_. Hopefully we all know what a function is
-> - a _method_ is simply the term used to refer to a function that is
+> between _functions_ and _methods_. Hopefully we all know what a function
+> is - a _method_ is simply the term used to refer to a function that is
 > associated with a specific object. Similarly, the term _attribute_ is used
 > to refer to some piece of information that is attached to an object, such as
 > `z.shape`, or `z.dtype`.
@@ -611,7 +606,7 @@ like what you might expect from Matlab.  You can find a brief overview of the
 ### Broadcasting
 
 
-One of the coolest features of Numpy is _broadcasting_<sup>3</sup>.
+One of the coolest features of Numpy is *broadcasting*<sup>3</sup>.
 Broadcasting allows you to perform element-wise operations on arrays which
 have a different shape. For each axis in the two arrays, Numpy will implicitly
 expand the shape of the smaller axis to match the shape of the larger one. You
@@ -963,9 +958,9 @@ correct:
 
 > <sup>4</sup> Even though these are FLIRT transforms, this is just a toy
 > example.  Look
-> [here](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FLIRT/FAQ#What_is_the_format_of_the_matrix_used_by_FLIRT.2C_and_how_does_it_relate_to_the_transformation_parameters.3F)
+> [here](https://users.fmrib.ox.ac.uk/~paulmc/fsleyes/fslpy/latest/fsl.transform.flirt.html)
 > and
-> [here](https://git.fmrib.ox.ac.uk/fsl/fslpy/blob/1.6.2/fsl/utils/transform.py#L537)
+> [here](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FLIRT/FAQ#What_is_the_format_of_the_matrix_used_by_FLIRT.2C_and_how_does_it_relate_to_the_transformation_parameters.3F)
 > if you actually need to work with FLIRT transforms.
 
 
-- 
GitLab