diff --git a/getting_started/01_basics.ipynb b/getting_started/01_basics.ipynb
index 677b36d559ec4b943240a696cc8fc7b4f8c603a0..416236015eebb1ae86daeb1dc377e8385364592c 100644
--- a/getting_started/01_basics.ipynb
+++ b/getting_started/01_basics.ipynb
@@ -69,7 +69,7 @@
     "* lists\n",
     "* dictionaries\n",
     "\n",
-    "N-dimensional arrays and other types are supported through common modules (e.g., numpy, scipy, scikit-learn).  These will be covered in a subsequent exercise."
+    "N-dimensional arrays and other types are supported through common modules (e.g., [numpy](https://numpy.org/), [scipy](https://docs.scipy.org/doc/scipy-1.4.1/reference/), [scikit-learn](https://scikit-learn.org/stable/)).  These will be covered in a subsequent exercises."
    ]
   },
   {
@@ -163,9 +163,8 @@
     "<a class=\"anchor\" id=\"Format\"></a>\n",
     "### Format\n",
     "\n",
-    "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:"
+    "More interesting strings can be created using an [f-string](https://realpython.com/python-f-strings/), \n",
+    "which is very useful in print statements:"
    ]
   },
   {
@@ -176,36 +175,21 @@
    "source": [
     "x = 1\n",
     "y = 'PyTreat'\n",
-    "s = 'The numerical value is {} and a name is {}'.format(x, y)\n",
+    "s = f'The numerical value is {x} and a name is {y}'\n",
     "print(s)\n",
-    "print('A name is {} and a number is {}'.format(y, x))"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "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))"
+    "print(f'A name is {y} and a number is {x}')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "Note the `f` before the initial quote. This lets python know to fill in the variables between the curly brackets.\n",
+    "\n",
+    "There are also other options along these lines, which will be discussed in the next practical. \n",
+    "This is the more modern version, although you will see plenty of the other alternatives in \"old\" code \n",
+    "(to python coders this means anything written before last week).\n",
+    "\n",
     "<a class=\"anchor\" id=\"String-manipulation\"></a>\n",
     "### String manipulation\n",
     "\n",
@@ -351,7 +335,7 @@
    "source": [
     "csvdata = 'some,comma,separated,data'\n",
     "tsvdata = '\\t'.join(csvdata.split(','))\n",
-    "tsvdata = tsvdata.replace('comma', 'tab'))\n",
+    "tsvdata = tsvdata.replace('comma', 'tab')\n",
     "print('csvdata:', csvdata)\n",
     "print('tsvdata:', tsvdata)"
    ]
@@ -460,7 +444,7 @@
    "metadata": {},
    "source": [
     "> 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",
+    "> `a += (80)` because a tuple is immutable so cannot be changed like this.\n",
     "\n",
     "<a class=\"anchor\" id=\"Indexing\"></a>\n",
     "### Indexing\n",
@@ -626,7 +610,7 @@
     "> _*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 this can be done in `numpy`)."
    ]
   },
   {
@@ -1066,7 +1050,7 @@
     "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",
+    "Relevant boolean and comparison operators include: `not`, `and`, `or`, `==` and `!=`.\n",
     "\n",
     "For example:"
    ]
@@ -1107,7 +1091,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "A useful keyword is `None`, which is a bit like \"null\". This can be a default value for a variable and should be tested with the `is` operator rather than `==` (for technical reasons that it isn't worth going into here). For example: `a is None` or `a is not None` are the preferred tests.\n",
+    "A useful keyword is `None`, which is a bit like \"null\". \n",
+    "This can be a default value for a variable and should be tested with the `is` operator rather than `==` (for technical reasons that it isn't worth going into here). For example: `a is None` or `a is not None` are the preferred tests.\n",
+    "Do not use the `is` instead of the `==` operator for any other comparisons (unless you know what you are doing).\n",
     "\n",
     "\n",
     "<a class=\"anchor\" id=\"If-statements\"></a>\n",
@@ -1191,7 +1177,7 @@
    "outputs": [],
    "source": [
     "for x in range(2, 9):\n",
-    "  print(x)"
+    "    print(x)"
    ]
   },
   {
diff --git a/getting_started/01_basics.md b/getting_started/01_basics.md
index ac9372c7e12bd688b410200a76fe65eefdc69517..e6e4ff661c07b614513ffd465a28835542e090a7 100644
--- a/getting_started/01_basics.md
+++ b/getting_started/01_basics.md
@@ -63,7 +63,7 @@ Python has many different types and variables are dynamic and can change types (
 * lists
 * dictionaries
 
-N-dimensional arrays and other types are supported through common modules (e.g., numpy, scipy, scikit-learn).  These will be covered in a subsequent exercise.
+N-dimensional arrays and other types are supported through common modules (e.g., [numpy](https://numpy.org/), [scipy](https://docs.scipy.org/doc/scipy-1.4.1/reference/), [scikit-learn](https://scikit-learn.org/stable/)).  These will be covered in a subsequent exercises.
 
 ```
 a = 4
@@ -114,28 +114,20 @@ print(s3)
 <a class="anchor" id="Format"></a>
 ### Format
 
-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:
-
+More interesting strings can be created using an [f-string](https://realpython.com/python-f-strings/), 
+which is very useful in print statements:
 ```
 x = 1
 y = 'PyTreat'
-s = 'The numerical value is {} and a name is {}'.format(x, y)
+s = f'The numerical value is {x} and a name is {y}'
 print(s)
-print('A name is {} and a number is {}'.format(y, x))
+print(f'A name is {y} and a number is {x}')
 ```
+Note the `f` before the initial quote. This lets python know to fill in the variables between the curly brackets.
 
-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))
-```
+There are also other options along these lines, which will be discussed in the next practical. 
+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).
 
 <a class="anchor" id="String-manipulation"></a>
 ### String manipulation
@@ -200,7 +192,7 @@ method, `join()`:
 ```
 csvdata = 'some,comma,separated,data'
 tsvdata = '\t'.join(csvdata.split(','))
-tsvdata = tsvdata.replace('comma', 'tab'))
+tsvdata = tsvdata.replace('comma', 'tab')
 print('csvdata:', csvdata)
 print('tsvdata:', tsvdata)
 ```
@@ -263,7 +255,7 @@ 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.
+> `a += (80)` because a tuple is immutable so cannot be changed like this.
 
 <a class="anchor" id="Indexing"></a>
 ### Indexing
@@ -337,7 +329,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 this can be done in `numpy`).
 
 ```
 b = [3, 4]
@@ -574,7 +566,7 @@ capitals). Other values can also be used for True or False (e.g., `1` for
 not considered 'equal' in the sense that the operator `==` would consider them
 the same.
 
-Relevant boolean and comparison operators include: `not`, `and`, `or`, `==` and `!=`
+Relevant boolean and comparison operators include: `not`, `and`, `or`, `==` and `!=`.
 
 For example:
 ```
@@ -594,7 +586,9 @@ print(3 in [1, 2, 3, 4])
 ```
 
 
-A useful keyword is `None`, which is a bit like "null". This can be a default value for a variable and should be tested with the `is` operator rather than `==` (for technical reasons that it isn't worth going into here). For example: `a is None` or `a is not None` are the preferred tests.
+A useful keyword is `None`, which is a bit like "null". 
+This can be a default value for a variable and should be tested with the `is` operator rather than `==` (for technical reasons that it isn't worth going into here). For example: `a is None` or `a is not None` are the preferred tests.
+Do not use the `is` instead of the `==` operator for any other comparisons (unless you know what you are doing).
 
 
 <a class="anchor" id="If-statements"></a>
@@ -636,7 +630,7 @@ where a list or any other sequence (e.g. tuple) can be used.
 If you want a numerical range then use:
 ```
 for x in range(2, 9):
-  print(x)
+    print(x)
 ```
 Note that, like slicing, the maximum value is one less than the value specified.  Also, `range` actually returns an object that can be iterated over but is not just a list of numbers. If you want a list of numbers then `list(range(2, 9))` will give you this.
 
diff --git a/getting_started/02_text_io.ipynb b/getting_started/02_text_io.ipynb
index 310b3adaf7c93d023898036863f087a6eaa9f199..7fd5e0f97c17ce1975a88ded9b3266742eb7a89f 100644
--- a/getting_started/02_text_io.ipynb
+++ b/getting_started/02_text_io.ipynb
@@ -45,15 +45,15 @@
    "source": [
     "* [Reading/writing files](#reading-writing-files)\n",
     "* [Creating new strings](#creating-new-strings)\n",
-    " * [String syntax](#string-syntax)\n",
-    "  * [Unicode versus bytes](#unicode-versus-bytes)\n",
-    " * [Converting objects into strings](#converting-objects-into-strings)\n",
-    " * [Combining strings](#combining-strings)\n",
-    " * [String formattings](#string-formatting)\n",
+    "  * [String syntax](#string-syntax)\n",
+    "    * [Unicode versus bytes](#unicode-versus-bytes)\n",
+    "  * [Converting objects into strings](#converting-objects-into-strings)\n",
+    "  * [Combining strings](#combining-strings)\n",
+    "  * [String formattings](#string-formatting)\n",
     "* [Extracting information from strings](#extracting-information-from-strings)\n",
-    " * [Splitting strings](#splitting-strings)\n",
-    " * [Converting strings to numbers](#converting-strings-to-numbers)\n",
-    " * [Regular expressions](#regular-expressions)\n",
+    "  * [Splitting strings](#splitting-strings)\n",
+    "  * [Converting strings to numbers](#converting-strings-to-numbers)\n",
+    "  * [Regular expressions](#regular-expressions)\n",
     "* [Exercises](#exercises)\n",
     "\n",
     "<a class=\"anchor\" id=\"reading-writing-files\"></a>\n",
@@ -67,7 +67,8 @@
     "\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",
+    "  file). A `'b'` can be added to any of these to open the file in \"byte\"-mode,\n",
+    "  which prevents python from interpreting non-text (e.g., NIFTI) files as text.\n",
     "\n",
     "* `file_object` is a variable name which will be used within the `block of\n",
     "  code` to access the opened file.\n",
@@ -111,7 +112,7 @@
     "        # 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",
+    "        print(f'{i}: {line.rstrip()}')\n",
     "        if i == 4:\n",
     "            break"
    ]
@@ -120,6 +121,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "> enumerate takes any sequence and returns 2-element tuples with the index and the sequence item\n",
+    "\n",
     "A very similar syntax is used to write files:"
    ]
   },
@@ -138,7 +141,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Note that no new line characters get added automatically. We can investigate\n",
+    "Note that new line characters do not get added automatically. We can investigate\n",
     "the resulting file using"
    ]
   },
@@ -243,6 +246,23 @@
     "print(a_string)"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "You can even include unicode characters:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a_string = \"Python = 🐍\"\n",
+    "print(a_string)"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -300,6 +320,8 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "> This will lead to a syntax warning in python 3.8 or greater\n",
+    "\n",
     "<a class=\"anchor\" id=\"unicode-versus-bytes\"></a>\n",
     "#### unicode versus bytes\n",
     "\n",
@@ -403,7 +425,7 @@
     "\n",
     "\n",
     "<a class=\"anchor\" id=\"converting-objects-into-strings\"></a>\n",
-    "### converting objects into strings\n",
+    "### Converting objects into strings\n",
     "\n",
     "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",
@@ -529,9 +551,9 @@
     "<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/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/reference/lexical_analysis.html#f-strings) (these are only available in python 3.6+)\n",
+    "* [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",
     "* 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",
@@ -599,7 +621,28 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "This code block will fail in fslpython, since it uses python 3.5.\n",
+    "These f-strings are extremely useful when creating print or error messages for debugging, \n",
+    "especially with the new support for self-documenting in python 3.8 (see \n",
+    "[here](https://docs.python.org/3/whatsnew/3.8.html#f-strings-support-for-self-documenting-expressions-and-debugging)):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a = 3\n",
+    "b = 1/3\n",
+    "\n",
+    "print(f'{a + b=}')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Note that this prints both the expression `a + b` and the output (this block will raise an error for python <= 3.7). \n",
     "\n",
     "\n",
     "<a class=\"anchor\" id=\"extracting-information-from-strings\"></a>\n",
@@ -614,7 +657,7 @@
     "\n",
     "<a class=\"anchor\" id=\"splitting-strings\"></a>\n",
     "### Splitting strings\n",
-    "The simplest way to extract a sub-string is to use slicing"
+    "The simplest way to extract a sub-string is to use slicing (see previous practical for more details):"
    ]
   },
   {
@@ -736,7 +779,7 @@
    "outputs": [],
    "source": [
     "input_filename = '02_text_io/input.txt'\n",
-    "out_filename = '02_text_io/output.txt'\n",
+    "output_filename = '02_text_io/output.txt'\n",
     "\n",
     "with open(input_filename, 'r') as input_file:\n",
     "    ...\n",
diff --git a/getting_started/02_text_io.md b/getting_started/02_text_io.md
index 42e93b09b5883d25ded5c0540d221bc3dfb540a1..be01e9c579ecedf3837b77e4a503a5659d8f0847 100644
--- a/getting_started/02_text_io.md
+++ b/getting_started/02_text_io.md
@@ -24,15 +24,15 @@ empty_string.
 
 * [Reading/writing files](#reading-writing-files)
 * [Creating new strings](#creating-new-strings)
- * [String syntax](#string-syntax)
-  * [Unicode versus bytes](#unicode-versus-bytes)
- * [Converting objects into strings](#converting-objects-into-strings)
- * [Combining strings](#combining-strings)
- * [String formattings](#string-formatting)
+  * [String syntax](#string-syntax)
+    * [Unicode versus bytes](#unicode-versus-bytes)
+  * [Converting objects into strings](#converting-objects-into-strings)
+  * [Combining strings](#combining-strings)
+  * [String formattings](#string-formatting)
 * [Extracting information from strings](#extracting-information-from-strings)
- * [Splitting strings](#splitting-strings)
- * [Converting strings to numbers](#converting-strings-to-numbers)
- * [Regular expressions](#regular-expressions)
+  * [Splitting strings](#splitting-strings)
+  * [Converting strings to numbers](#converting-strings-to-numbers)
+  * [Regular expressions](#regular-expressions)
 * [Exercises](#exercises)
 
 <a class="anchor" id="reading-writing-files"></a>
@@ -46,7 +46,8 @@ The syntax to open a file in python is `with open(<filename>, <mode>) as
 
 * `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). A `'b'` can be added to any of these to open the file in "byte"-mode,
+  which prevents python from interpreting non-text (e.g., NIFTI) files as text.
 
 * `file_object` is a variable name which will be used within the `block of
   code` to access the opened file.
@@ -73,11 +74,13 @@ with open('README.md', 'r') as readme_file:
         # each line is returned with its
         # newline character still intact,
         # so we use rstrip() to remove it.
-        print('{}: {}'.format(i, line.rstrip()))
+        print(f'{i}: {line.rstrip()}')
         if i == 4:
             break
 ```
 
+> enumerate takes any sequence and returns 2-element tuples with the index and the sequence item
+
 A very similar syntax is used to write files:
 ```
 with open('02_text_io/my_file', 'w') as my_file:
@@ -85,7 +88,7 @@ with open('02_text_io/my_file', 'w') as my_file:
     my_file.writelines(['Second line\n', 'and the third\n'])
 ```
 
-Note that no new line characters get added automatically. We can investigate
+Note that new line characters do not get added automatically. We can investigate
 the resulting file using
 
 ```
@@ -143,6 +146,12 @@ a_string = "This is the first line.\nAnd here is the second.\n\tThe third starts
 print(a_string)
 ```
 
+You can even include unicode characters:
+```
+a_string = "Python = 🐍"
+print(a_string)
+```
+
 However, the easiest way to create multi-line strings is to use a triple quote (again single or double quotes can be used). Triple quotes allow your string to span multiple lines:
 ```
 multi_line_string = """This is the first line.
@@ -162,6 +171,7 @@ One pitfall when creating a list of strings is that python automatically concate
 my_list_of_strings = ['a', 'b', 'c' 'd', 'e']
 print("The 'c' and 'd' got concatenated, because we forgot the comma:", my_list_of_strings)
 ```
+> This will lead to a syntax warning in python 3.8 or greater
 
 <a class="anchor" id="unicode-versus-bytes"></a>
 #### unicode versus bytes
@@ -227,7 +237,7 @@ with open(op.expandvars('${FSLDIR}/data/standard/MNI152_T1_1mm.nii.gz'), 'rb') a
 
 
 <a class="anchor" id="converting-objects-into-strings"></a>
-### converting objects into strings
+### 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
@@ -286,9 +296,9 @@ 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/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/reference/lexical_analysis.html#f-strings) (these are only available in python 3.6+)
+* [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)
 * 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.
@@ -321,7 +331,17 @@ b = 1/3
 
 print(f'{a + b:.3f} = {a} + {b:.3f}')
 ```
-This code block will fail in fslpython, since it uses python 3.5.
+
+These f-strings are extremely useful when creating print or error messages for debugging, 
+especially with the new support for self-documenting in python 3.8 (see 
+[here](https://docs.python.org/3/whatsnew/3.8.html#f-strings-support-for-self-documenting-expressions-and-debugging)):
+```
+a = 3
+b = 1/3
+
+print(f'{a + b=}')
+```
+Note that this prints both the expression `a + b` and the output (this block will raise an error for python <= 3.7). 
 
 
 <a class="anchor" id="extracting-information-from-strings"></a>
@@ -336,7 +356,7 @@ practcals.
 
 <a class="anchor" id="splitting-strings"></a>
 ### Splitting strings
-The simplest way to extract a sub-string is to use slicing
+The simplest way to extract a sub-string is to use slicing (see previous practical for more details):
 ```
 a_string = 'abcdefghijklmnopqrstuvwxyz'
 print(a_string[10])  # create a string containing only the 11th character
@@ -398,7 +418,7 @@ The file 02_text_io/input.txt contains integers in a 2-column format (separated
 
 ```
 input_filename = '02_text_io/input.txt'
-out_filename = '02_text_io/output.txt'
+output_filename = '02_text_io/output.txt'
 
 with open(input_filename, 'r') as input_file:
     ...
diff --git a/getting_started/03_file_management.ipynb b/getting_started/03_file_management.ipynb
index a677f0d0867b13e45ce59782acd44b90dc6d7447..7b010c175020f258490ef69b735a0298bf2f361b 100644
--- a/getting_started/03_file_management.ipynb
+++ b/getting_started/03_file_management.ipynb
@@ -42,23 +42,24 @@
     "\n",
     "\n",
     "* [Managing files and directories](#managing-files-and-directories)\n",
-    " * [Querying and changing the current directory](#querying-and-changing-the-current-directory)\n",
-    " * [Directory listings](#directory-listings)\n",
-    " * [Creating and removing directories](#creating-and-removing-directories)\n",
-    " * [Moving and removing files](#moving-and-removing-files)\n",
-    " * [Walking a directory tree](#walking-a-directory-tree)\n",
-    " * [Copying, moving, and removing directory trees](#copying-moving-and-removing-directory-trees)\n",
+    "  * [Querying and changing the current directory](#querying-and-changing-the-current-directory)\n",
+    "  * [Directory listings](#directory-listings)\n",
+    "  * [Creating and removing directories](#creating-and-removing-directories)\n",
+    "  * [Moving and removing files](#moving-and-removing-files)\n",
+    "  * [Walking a directory tree](#walking-a-directory-tree)\n",
+    "  * [Copying, moving, and removing directory trees](#copying-moving-and-removing-directory-trees)\n",
     "* [Managing file paths](#managing-file-paths)\n",
-    " * [File and directory tests](#file-and-directory-tests)\n",
-    " * [Deconstructing paths](#deconstructing-paths)\n",
-    " * [Absolute and relative paths](#absolute-and-relative-paths)\n",
-    " * [Wildcard matching a.k.a. globbing](#wildcard-matching-aka-globbing)\n",
-    " * [Expanding the home directory and environment variables](#expanding-the-home-directory-and-environment-variables)\n",
-    " * [Cross-platform compatibility](#cross-platform-compatbility)\n",
+    "  * [File and directory tests](#file-and-directory-tests)\n",
+    "  * [Deconstructing paths](#deconstructing-paths)\n",
+    "  * [Absolute and relative paths](#absolute-and-relative-paths)\n",
+    "  * [Wildcard matching a.k.a. globbing](#wildcard-matching-aka-globbing)\n",
+    "  * [Expanding the home directory and environment variables](#expanding-the-home-directory-and-environment-variables)\n",
+    "  * [Cross-platform compatibility](#cross-platform-compatbility)\n",
+    "* [FileTree](#filetree)\n",
     "* [Exercises](#exercises)\n",
-    " * [Re-name subject directories](#re-name-subject-directories)\n",
-    " * [Re-organise a data set](#re-organise-a-data-set)\n",
-    " * [Solutions](#solutions)\n",
+    "  * [Re-name subject directories](#re-name-subject-directories)\n",
+    "  * [Re-organise a data set](#re-organise-a-data-set)\n",
+    "  * [Solutions](#solutions)\n",
     "* [Appendix: Exceptions](#appendix-exceptions)\n",
     "\n",
     "\n",
@@ -79,7 +80,8 @@
    "outputs": [],
    "source": [
     "import os\n",
-    "import os.path as op"
+    "import os.path as op\n",
+    "from pathlib import Path"
    ]
   },
   {
@@ -111,13 +113,13 @@
    "outputs": [],
    "source": [
     "cwd = os.getcwd()\n",
-    "print('Current directory: {}'.format(cwd))\n",
+    "print(f'Current directory: {cwd}')\n",
     "\n",
     "os.chdir(op.expanduser('~'))\n",
-    "print('Changed to: {}'.format(os.getcwd()))\n",
+    "print(f'Changed to: {os.getcwd()}')\n",
     "\n",
     "os.chdir(cwd)\n",
-    "print('Changed back to: {}'.format(cwd))"
+    "print(f'Changed back to: {cwd}')"
    ]
   },
   {
@@ -158,13 +160,13 @@
    "source": [
     "cwd = os.getcwd()\n",
     "listing = os.listdir(cwd)\n",
-    "print('Directory listing: {}'.format(cwd))\n",
+    "print(f'Directory listing: {cwd}')\n",
     "print('\\n'.join(listing))\n",
     "print()\n",
     "\n",
     "datadir = 'raw_mri_data'\n",
     "listing = os.listdir(datadir)\n",
-    "print('Directory listing: {}'.format(datadir))\n",
+    "print(f'Directory listing: {datadir}')\n",
     "print('\\n'.join(listing))"
    ]
   },
@@ -961,6 +963,40 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "> The `Path` object in the built-in [`pathlib`](https://docs.python.org/3/library/pathlib.html) also has\n",
+    "> excellent cross-platform support. If you write your file management code using this class you are far less likely\n",
+    "> to get errors on Windows.\n",
+    "\n",
+    "<a class=\"anchor\" id=\"filetree\"></a>\n",
+    "## FileTree\n",
+    "`fslpy` also contains support for `FileTree` objects (docs are \n",
+    "[here](https://users.fmrib.ox.ac.uk/~paulmc/fsleyes/fslpy/latest/fsl.utils.filetree.html)). \n",
+    "These `FileTree` objects provide a simple format to define a whole directory structure with multiple subjects, sessions,\n",
+    "scans, etc. In the `FileTree` format the dataset we have been looking at so far would be described by:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "tree_text = \"\"\"\n",
+    "raw_mri_data\n",
+    "    subj_{subject}\n",
+    "        rest.nii.gz\n",
+    "        t1.nii\n",
+    "        t2.nii\n",
+    "        task.nii.gz\n",
+    "\"\"\""
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "FileTrees are discussed in more detail in the advanced `fslpy` practical.\n",
+    "\n",
     "<a class=\"anchor\" id=\"exercises\"></a>\n",
     "## Exercises\n",
     "\n",
diff --git a/getting_started/03_file_management.md b/getting_started/03_file_management.md
index 0c4979a8a3321811f0381fdfb5277b63c75d999d..82b259c5657e461b3e663565d0ac624d3a7e6486 100644
--- a/getting_started/03_file_management.md
+++ b/getting_started/03_file_management.md
@@ -36,23 +36,24 @@ other sections as a reference. You might miss out on some neat tricks though.
 
 
 * [Managing files and directories](#managing-files-and-directories)
- * [Querying and changing the current directory](#querying-and-changing-the-current-directory)
- * [Directory listings](#directory-listings)
- * [Creating and removing directories](#creating-and-removing-directories)
- * [Moving and removing files](#moving-and-removing-files)
- * [Walking a directory tree](#walking-a-directory-tree)
- * [Copying, moving, and removing directory trees](#copying-moving-and-removing-directory-trees)
+  * [Querying and changing the current directory](#querying-and-changing-the-current-directory)
+  * [Directory listings](#directory-listings)
+  * [Creating and removing directories](#creating-and-removing-directories)
+  * [Moving and removing files](#moving-and-removing-files)
+  * [Walking a directory tree](#walking-a-directory-tree)
+  * [Copying, moving, and removing directory trees](#copying-moving-and-removing-directory-trees)
 * [Managing file paths](#managing-file-paths)
- * [File and directory tests](#file-and-directory-tests)
- * [Deconstructing paths](#deconstructing-paths)
- * [Absolute and relative paths](#absolute-and-relative-paths)
- * [Wildcard matching a.k.a. globbing](#wildcard-matching-aka-globbing)
- * [Expanding the home directory and environment variables](#expanding-the-home-directory-and-environment-variables)
- * [Cross-platform compatibility](#cross-platform-compatbility)
+  * [File and directory tests](#file-and-directory-tests)
+  * [Deconstructing paths](#deconstructing-paths)
+  * [Absolute and relative paths](#absolute-and-relative-paths)
+  * [Wildcard matching a.k.a. globbing](#wildcard-matching-aka-globbing)
+  * [Expanding the home directory and environment variables](#expanding-the-home-directory-and-environment-variables)
+  * [Cross-platform compatibility](#cross-platform-compatbility)
+* [FileTree](#filetree)
 * [Exercises](#exercises)
- * [Re-name subject directories](#re-name-subject-directories)
- * [Re-organise a data set](#re-organise-a-data-set)
- * [Solutions](#solutions)
+  * [Re-name subject directories](#re-name-subject-directories)
+  * [Re-organise a data set](#re-organise-a-data-set)
+  * [Solutions](#solutions)
 * [Appendix: Exceptions](#appendix-exceptions)
 
 
@@ -69,6 +70,7 @@ creating, removing, and traversing directories.
 ```
 import os
 import os.path as op
+from pathlib import Path
 ```
 
 
@@ -92,13 +94,13 @@ You can query and change the current directory with the `os.getcwd` and
 
 ```
 cwd = os.getcwd()
-print('Current directory: {}'.format(cwd))
+print(f'Current directory: {cwd}')
 
 os.chdir(op.expanduser('~'))
-print('Changed to: {}'.format(os.getcwd()))
+print(f'Changed to: {os.getcwd()}')
 
 os.chdir(cwd)
-print('Changed back to: {}'.format(cwd))
+print(f'Changed back to: {cwd}')
 ```
 
 
@@ -123,13 +125,13 @@ command):
 ```
 cwd = os.getcwd()
 listing = os.listdir(cwd)
-print('Directory listing: {}'.format(cwd))
+print(f'Directory listing: {cwd}')
 print('\n'.join(listing))
 print()
 
 datadir = 'raw_mri_data'
 listing = os.listdir(datadir)
-print('Directory listing: {}'.format(datadir))
+print(f'Directory listing: {datadir}')
 print('\n'.join(listing))
 ```
 
@@ -730,6 +732,27 @@ print(path)
 print(op.join(op.sep, 'home', 'fsluser', '.bash_profile'))
 ```
 
+> The `Path` object in the built-in [`pathlib`](https://docs.python.org/3/library/pathlib.html) also has
+> excellent cross-platform support. If you write your file management code using this class you are far less likely
+> to get errors on Windows.
+
+<a class="anchor" id="filetree"></a>
+## FileTree
+`fslpy` also contains support for `FileTree` objects (docs are 
+[here](https://users.fmrib.ox.ac.uk/~paulmc/fsleyes/fslpy/latest/fsl.utils.filetree.html)). 
+These `FileTree` objects provide a simple format to define a whole directory structure with multiple subjects, sessions,
+scans, etc. In the `FileTree` format the dataset we have been looking at so far would be described by:
+```
+tree_text = """
+raw_mri_data
+    subj_{subject}
+        rest.nii.gz
+        t1.nii
+        t2.nii
+        task.nii.gz
+"""
+```
+FileTrees are discussed in more detail in the advanced `fslpy` practical.
 
 <a class="anchor" id="exercises"></a>
 ## Exercises
diff --git a/getting_started/04_numpy.ipynb b/getting_started/04_numpy.ipynb
index ce634dd4a41927e607d585cd04a6862296e56e46..7a198100cea817dc7adef89a7f32c79517b02b11 100644
--- a/getting_started/04_numpy.ipynb
+++ b/getting_started/04_numpy.ipynb
@@ -24,24 +24,24 @@
     "\n",
     "* [The Python list versus the Numpy array](#the-python-list-versus-the-numpy-array)\n",
     "* [Numpy basics](#numpy-basics)\n",
-    " * [Creating arrays](#creating-arrays)\n",
-    " * [Loading text files](#loading-text-files)\n",
-    " * [Array properties](#array-properties)\n",
-    " * [Descriptive statistics](#descriptive-statistics)\n",
-    " * [Reshaping and rearranging arrays](#reshaping-and-rearranging-arrays)\n",
+    "  * [Creating arrays](#creating-arrays)\n",
+    "  * [Loading text files](#loading-text-files)\n",
+    "  * [Array properties](#array-properties)\n",
+    "  * [Descriptive statistics](#descriptive-statistics)\n",
+    "  * [Reshaping and rearranging arrays](#reshaping-and-rearranging-arrays)\n",
     "* [Operating on arrays](#operating-on-arrays)\n",
-    " * [Scalar operations](#scalar-operations)\n",
-    " * [Multi-variate operations](#multi-variate-operations)\n",
-    " * [Matrix multplication](#matrix-multiplication)\n",
-    " * [Broadcasting](#broadcasting)\n",
-    " * [Linear algebra](#linear-algebra)\n",
+    "  * [Scalar operations](#scalar-operations)\n",
+    "  * [Multi-variate operations](#multi-variate-operations)\n",
+    "  * [Matrix multplication](#matrix-multiplication)\n",
+    "  * [Broadcasting](#broadcasting)\n",
+    "  * [Linear algebra](#linear-algebra)\n",
     "* [Array indexing](#array-indexing)\n",
-    " * [Indexing multi-dimensional arrays](#indexing-multi-dimensional-arrays)\n",
-    " * [Boolean indexing](#boolean-indexing)\n",
-    " * [Coordinate array indexing](#coordinate-array-indexing)\n",
+    "  * [Indexing multi-dimensional arrays](#indexing-multi-dimensional-arrays)\n",
+    "  * [Boolean indexing](#boolean-indexing)\n",
+    "  * [Coordinate array indexing](#coordinate-array-indexing)\n",
     "* [Exercises](#exercises)\n",
-    " * [Load an array from a file and do stuff with it](#load-an-array-from-a-file-and-do-stuff-with-it)\n",
-    " * [Concatenate affine transforms](#concatenate-affine-transforms)\n",
+    "  * [Load an array from a file and do stuff with it](#load-an-array-from-a-file-and-do-stuff-with-it)\n",
+    "  * [Concatenate affine transforms](#concatenate-affine-transforms)\n",
     "\n",
     "* [Appendix A: Generating random numbers](#appendix-generating-random-numbers)\n",
     "* [Appendix B: Importing Numpy](#appendix-importing-numpy)\n",
@@ -626,6 +626,9 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "Alternatively, you can use the `stack` function and give the index of the dimension along which the array\n",
+    "should be stacked as the `axis` keyword (so, `np.vstack((a, b))` is equivalent to `np.stack((a, b), axis=1)`). \n",
+    "\n",
     "<a class=\"anchor\" id=\"operating-on-arrays\"></a>\n",
     "## Operating on arrays\n",
     "\n",
@@ -892,6 +895,16 @@
     "and you can't figure out why refer to the [official\n",
     "documentation](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html).\n",
     "\n",
+    "In short the broadcasting rules are:\n",
+    "1. If the input arrays have a different number of dimensions, the ones with fewer \n",
+    "    dimensions will have new dimensions with length 1 prepended until all arrays \n",
+    "    have the same number of dimensions. So adding a 2D array shaped (3, 3) with\n",
+    "    a 1D array of length (3, ), is equivalent to adding the two 2D arrays with\n",
+    "    shapes (3, 3) and (1, 3).\n",
+    "2. Once, all the arrays have the same number of dimensions, the arrays are combined\n",
+    "    elementwise. Each dimension is compatible between the two arrays if they have \n",
+    "    equal length or one has a length of 1. In the latter case the dimension will\n",
+    "    be repeated using a procedure equivalent to Matlab's `repmat`).\n",
     "\n",
     "<a class=\"anchor\" id=\"linear-algebra\"></a>\n",
     "### Linear algebra\n",
@@ -1211,8 +1224,8 @@
     "\n",
     "evenrows, evencols = np.where(a % 2 == 0)\n",
     "\n",
-    "print('even row coordinates:', evenx)\n",
-    "print('even col coordinates:', eveny)\n",
+    "print('even row coordinates:', evenrows)\n",
+    "print('even col coordinates:', evencols)\n",
     "\n",
     "print(a[evenrows, evencols])"
    ]
diff --git a/getting_started/04_numpy.md b/getting_started/04_numpy.md
index db6fcf904f7a5a9b7d1c0d2739f183f2db2e9cf7..fbd50a03147b7cd8dee7ced9f0f27ab336e3bf2d 100644
--- a/getting_started/04_numpy.md
+++ b/getting_started/04_numpy.md
@@ -18,24 +18,24 @@ alternative to Matlab as a scientific computing platform.
 
 * [The Python list versus the Numpy array](#the-python-list-versus-the-numpy-array)
 * [Numpy basics](#numpy-basics)
- * [Creating arrays](#creating-arrays)
- * [Loading text files](#loading-text-files)
- * [Array properties](#array-properties)
- * [Descriptive statistics](#descriptive-statistics)
- * [Reshaping and rearranging arrays](#reshaping-and-rearranging-arrays)
+  * [Creating arrays](#creating-arrays)
+  * [Loading text files](#loading-text-files)
+  * [Array properties](#array-properties)
+  * [Descriptive statistics](#descriptive-statistics)
+  * [Reshaping and rearranging arrays](#reshaping-and-rearranging-arrays)
 * [Operating on arrays](#operating-on-arrays)
- * [Scalar operations](#scalar-operations)
- * [Multi-variate operations](#multi-variate-operations)
- * [Matrix multplication](#matrix-multiplication)
- * [Broadcasting](#broadcasting)
- * [Linear algebra](#linear-algebra)
+  * [Scalar operations](#scalar-operations)
+  * [Multi-variate operations](#multi-variate-operations)
+  * [Matrix multplication](#matrix-multiplication)
+  * [Broadcasting](#broadcasting)
+  * [Linear algebra](#linear-algebra)
 * [Array indexing](#array-indexing)
- * [Indexing multi-dimensional arrays](#indexing-multi-dimensional-arrays)
- * [Boolean indexing](#boolean-indexing)
- * [Coordinate array indexing](#coordinate-array-indexing)
+  * [Indexing multi-dimensional arrays](#indexing-multi-dimensional-arrays)
+  * [Boolean indexing](#boolean-indexing)
+  * [Coordinate array indexing](#coordinate-array-indexing)
 * [Exercises](#exercises)
- * [Load an array from a file and do stuff with it](#load-an-array-from-a-file-and-do-stuff-with-it)
- * [Concatenate affine transforms](#concatenate-affine-transforms)
+  * [Load an array from a file and do stuff with it](#load-an-array-from-a-file-and-do-stuff-with-it)
+  * [Concatenate affine transforms](#concatenate-affine-transforms)
 
 * [Appendix A: Generating random numbers](#appendix-generating-random-numbers)
 * [Appendix B: Importing Numpy](#appendix-importing-numpy)
@@ -457,6 +457,8 @@ print('dstacked: (shape {}):'.format(dstacked.shape))
 print( dstacked)
 ```
 
+Alternatively, you can use the `stack` function and give the index of the dimension along which the array
+should be stacked as the `axis` keyword (so, `np.vstack((a, b))` is equivalent to `np.stack((a, b), axis=1)`). 
 
 <a class="anchor" id="operating-on-arrays"></a>
 ## Operating on arrays
@@ -655,8 +657,6 @@ print(a - a.mean(axis=0))
 print('a (rows demeaned):')
 print(a - a.mean(axis=1).reshape(-1, 1))
 ```
-
-
 > As demonstrated above, many functions in Numpy accept an `axis` parameter,
 > allowing you to apply the function along a specific axis. Omitting the
 > `axis` parameter will apply the function to the whole array.
@@ -668,6 +668,16 @@ should be applied, are pretty straightforward. If something is not working,
 and you can't figure out why refer to the [official
 documentation](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html).
 
+In short the broadcasting rules are:
+1. If the input arrays have a different number of dimensions, the ones with fewer 
+    dimensions will have new dimensions with length 1 prepended until all arrays 
+    have the same number of dimensions. So adding a 2D array shaped (3, 3) with
+    a 1D array of length (3, ), is equivalent to adding the two 2D arrays with
+    shapes (3, 3) and (1, 3).
+2. Once, all the arrays have the same number of dimensions, the arrays are combined
+    elementwise. Each dimension is compatible between the two arrays if they have 
+    equal length or one has a length of 1. In the latter case the dimension will
+    be repeated using a procedure equivalent to Matlab's `repmat`).
 
 <a class="anchor" id="linear-algebra"></a>
 ### Linear algebra
@@ -904,8 +914,8 @@ print(a)
 
 evenrows, evencols = np.where(a % 2 == 0)
 
-print('even row coordinates:', evenx)
-print('even col coordinates:', eveny)
+print('even row coordinates:', evenrows)
+print('even col coordinates:', evencols)
 
 print(a[evenrows, evencols])
 ```
diff --git a/getting_started/05_nifti.ipynb b/getting_started/05_nifti.ipynb
index 9d8551e0970ce313c6801143e06d5b4859db3b2d..fdf354d748f7becaa9ff4ce87df6119b1c308892 100644
--- a/getting_started/05_nifti.ipynb
+++ b/getting_started/05_nifti.ipynb
@@ -22,8 +22,8 @@
     "\n",
     "* [Reading images](#reading-images)\n",
     "* [Header info](#header-info)\n",
-    " * [Voxel sizes](#voxel-sizes)\n",
-    " * [Coordinate orientations and mappings](#orientation-info)\n",
+    "  * [Voxel sizes](#voxel-sizes)\n",
+    "  * [Coordinate orientations and mappings](#orientation-info)\n",
     "* [Writing images](#writing-images)\n",
     "* [Exercise](#exercise)\n",
     "\n",
@@ -32,7 +32,7 @@
     "<a class=\"anchor\" id=\"reading-images\"></a>\n",
     "## Reading images\n",
     "\n",
-    "It is easy to read an image:"
+    "For most neuroimaging dataformats reading an image is as simple as calling `nibabel.load`."
    ]
   },
   {
@@ -49,10 +49,11 @@
     "\n",
     "# display header object\n",
     "imhdr = imobj.header\n",
+    "print('header', imhdr)\n",
     "\n",
     "# extract data (as a numpy array)\n",
     "imdat = imobj.get_fdata()\n",
-    "print(imdat.shape)"
+    "print('data', imdat.shape)"
    ]
   },
   {
@@ -152,6 +153,26 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "If you don't want to have to worry about the difference between `qform` and `sform`,\n",
+    "you can just let `nibabel` return what it thinks is the appropriate `affine`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print('affine', imobj.affine) "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Note that we access the `affine` attribute from the image object here, not the image header (like above).\n",
+    "> Accessing the affine this way has the advantage that it will also work for data types, where the affine is stored in a different way in the header.\n",
+    "\n",
     "---\n",
     "\n",
     "<a class=\"anchor\" id=\"writing-images\"></a>\n",
diff --git a/getting_started/05_nifti.md b/getting_started/05_nifti.md
index 67139c538b3f79da2dd24057f8e82acaae4cebe7..2b6b87ed6b34beb2b7bf133c525f24a12ac20286 100644
--- a/getting_started/05_nifti.md
+++ b/getting_started/05_nifti.md
@@ -16,8 +16,8 @@ practical (`advanced_topics/08_fslpy.ipynb`).
 
 * [Reading images](#reading-images)
 * [Header info](#header-info)
- * [Voxel sizes](#voxel-sizes)
- * [Coordinate orientations and mappings](#orientation-info)
+  * [Voxel sizes](#voxel-sizes)
+  * [Coordinate orientations and mappings](#orientation-info)
 * [Writing images](#writing-images)
 * [Exercise](#exercise)
 
@@ -26,7 +26,7 @@ practical (`advanced_topics/08_fslpy.ipynb`).
 <a class="anchor" id="reading-images"></a>
 ## Reading images
 
-It is easy to read an image:
+For most neuroimaging dataformats reading an image is as simple as calling `nibabel.load`.
 
 ```
 import numpy as np
@@ -37,10 +37,11 @@ imobj = nib.load(filename, mmap=False)
 
 # display header object
 imhdr = imobj.header
+print('header', imhdr)
 
 # extract data (as a numpy array)
 imdat = imobj.get_fdata()
-print(imdat.shape)
+print('data', imdat.shape)
 ```
 
 > Make sure you use the full filename, including the `.nii.gz` extension.
@@ -105,6 +106,13 @@ affine, code = imhdr.get_qform(coded=True)
 print(affine, code)
 ```
 
+If you don't want to have to worry about the difference between `qform` and `sform`,
+you can just let `nibabel` return what it thinks is the appropriate `affine`:
+```
+print('affine', imobj.affine) 
+```
+> Note that we access the `affine` attribute from the image object here, not the image header (like above).
+> Accessing the affine this way has the advantage that it will also work for data types, where the affine is stored in a different way in the header.
 
 ---