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. ---