diff --git a/advanced_topics/object_oriented_programming.ipynb b/advanced_topics/object_oriented_programming.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..1e406f53f47335fa024412d50fddfe4719525ff3 --- /dev/null +++ b/advanced_topics/object_oriented_programming.ipynb @@ -0,0 +1,514 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Object-oriented programming\n", + "\n", + "\n", + "By now you might have realised that __everything__ in Python is an\n", + "object. Strings are objects, numbers are objects, functions are objects,\n", + "modules are objects - __everything__ is an object!\n", + "\n", + "\n", + "But this does not mean that you have to use Python in an object-oriented\n", + "fashion. You can stick with functions and statements, and get quite a lot\n", + "done. But some problems are just easier to solve, and to reason about, when\n", + "you use an object-oriented approach.\n", + "\n", + "\n", + "## Objects versus classes\n", + "\n", + "\n", + "If you are versed in C++, Java, C#, or some other object-oriented language,\n", + "then this should all hopefully sound familiar, and you can skip to the next\n", + "section.\n", + "\n", + "\n", + "If you have not done any object-oriented programming before, your first step\n", + "is to understand the difference between _objects_ (also known as _instances_)\n", + "and _classes_ (also known as _types_).\n", + "\n", + "\n", + "If you have some experience in C, then you can start off by thinking of a\n", + "class as like a `struct` definition - it is a specification for the layout of\n", + "a chunk of memory. For example, here is a typical struct definition:\n", + "\n", + "> ```\n", + "> /**\n", + "> * Struct representing a stack.\n", + "> */\n", + "> typedef struct __stack {\n", + "> uint8_t capacity; /**< the maximum capacity of this stack */\n", + "> uint8_t size; /**< the current size of this stack */\n", + "> void **top; /**< pointer to the top of this stack */\n", + "> } stack_t;\n", + "> ```\n", + "\n", + "\n", + "Now, an _object_ is not a definition, but rather a thing which resides in\n", + "memory. An object can have _attributes_ (pieces of information), and _methods_\n", + "(functions associated with the object). You can pass objects around your code,\n", + "manipulate their attributes, and call their methods.\n", + "\n", + "\n", + "Returning to our C metaphor, you can think of an object as like an\n", + "instantiation of a struct:\n", + "\n", + "\n", + "> ```\n", + "> stack_t stack;\n", + "> stack.capacity = 10;\n", + "> ```\n", + "\n", + "\n", + "The fundamental difference between a `struct` in C, and a `class` in Python\n", + "and other object oriented languages, is that you can't (easily) add functions\n", + "to a `struct` - it is just a chunk of memory. Whereas in Python, you can add\n", + "functions to your class definition, which will then be added as methods when\n", + "you create an object from that class.\n", + "\n", + "\n", + "Of course there are many more differences between C structs and classes (most\n", + "notably [inheritance](todo), and [protection](todo)). But if you can\n", + "understand the difference between a _definition_ of a C struct, and an\n", + "_instantiation_ of that struct, then you are most of the way towards\n", + "understanding the difference between a Python _class_, and a Python _object_.\n", + "\n", + "\n", + "> But just to confuse you, remember that in Python, __everything__ is an\n", + "> object - even classes!\n", + "\n", + "\n", + "## Defining a class\n", + "\n", + "\n", + "Defining a class in Python is simple. Let's take on a small project, by\n", + "developing a class which can be used in place of the `fslmaths` shell command." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class FSLMaths(object):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this statement, we defined a new class called `FSLMaths`, which inherits\n", + "from the built-in `object` base-class (see [below](todo) for more details on\n", + "inheritance).\n", + "\n", + "\n", + "Now that we have defined our class, we can create objects - instances of that\n", + "class - by calling the class itself, as if it were a function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fm1 = FSLMaths()\n", + "fm2 = FSLMaths()\n", + "print(fm1)\n", + "print(fm2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Although these objects are not of much use at this stage. Let's do some more\n", + "work.\n", + "\n", + "\n", + "## Object creation - the `__init__` method\n", + "\n", + "\n", + "The first thing that our `fslmaths` replacement will need is an input image.\n", + "It makes sense to pass this in when we create an `FSLMaths` object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class FSLMaths(object):\n", + " def __init__(self, inimg):\n", + " self.input = inimg" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we have added a _method_ called `__init__` to our class (remember that a\n", + "_method_ is just a function which is associated with a specific object). This\n", + "method expects two arguments - `self`, and `inimg`. So now, when we create an\n", + "instance of the `FSLMaths` class, we will need to provide an input image:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import nibabel as nib\n", + "import os.path as op\n", + "\n", + "input = op.expandvars('$FSLDIR/data/standard/MNI152_T1_2mm.nii.gz')\n", + "inimg = nib.load(input)\n", + "fm = FSLMaths(inimg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are a couple of things to note here:\n", + "\n", + "\n", + "__Our method is called__ `__init__`__, but we didn't actually call the__\n", + "`__init__` __method!__ `__init__` is a special method in Python - it is called\n", + "when an instance of a class is created. And recall that we can create an\n", + "instance of a class by calling the class in the same way that we call a\n", + "function.\n", + "\n", + "\n", + "__We didn't specify the `self` argument - what gives?!?__ The `self` argument\n", + "is a special argument that is specific to methods in Python. If you are coming\n", + "from C++, Java, C# or similar, `self` in Python is equivalent to `this` in\n", + "those languages.\n", + "\n", + "\n", + "### The `self` argument\n", + "\n", + "\n", + "In a method, the `self` argument is a reference to the object that the method\n", + "was called on. So in this line of code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fm = FSLMaths(inimg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "the `self` argument in `__init__` will be a reference to the `FSLMaths` object\n", + "that has been created (and is then assigned to the `fm` variable, after the\n", + "`__init__` method has finished).\n", + "\n", + "\n", + "But note that we do not need to provide the `self` argument - this is a quirk\n", + "specific to methods in Python - when you call a method on an object (or a\n", + "class, to create a new object), the Python runtime will take care of passing\n", + "the instance as the `self` argument to to the method.\n", + "\n", + "\n", + "But when you are writing a class, you _do_ need to explicitly list `self` as\n", + "the first argument to all of the methods of the class.\n", + "\n", + "\n", + "## Attributes\n", + "\n", + "\n", + "In Python, the term _attribute_ is used to refer to a piece of information\n", + "that is associated with an object. An attribute is generally a reference to\n", + "another object (which might be a string, a number, or a list, or some other\n", + "more complicated object).\n", + "\n", + "\n", + "Remember that we modified our `FSLMaths` class so that it is passed an input\n", + "image on creation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class FSLMaths(object):\n", + " def __init__(self, inimg):\n", + " self.input = inimg\n", + "\n", + "input = op.expanduser('$FSLDIR/data/standard/MNI152_T1_2mm.nii.gz')\n", + "fm = FSLMaths(nib.load(input))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Take a look at what is going on in the `__init__` method - we take the `inimg`\n", + "argument, and create a reference to it called `self.input`. We have added an\n", + "_attribute_ to the `FSLMaths` instance, called `input`, and we can access that\n", + "attribute like so:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('Input for our FSLMaths instance: {}'.format(fm.input))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And that concludes the section on adding attributes to Python objects.\n", + "\n", + "\n", + "Just kidding. But it really is that simple. This is one aspect of Python which\n", + "might be quite jarring to you if you are coming from a language with more\n", + "rigid semantics, such as C++ or Java. In those languages, you must pre-specify\n", + "all of the attributes and methods that are a part of a class. But Python works\n", + "a bit differently - you add attributes to an object affer it has been created.\n", + "In fact, you can even do this outside of the class definition<sup>1</sup>:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fm = FSLMaths(inimg)\n", + "fm.another_attribute = 'Haha'\n", + "print(fm.another_attribute)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> <sup>1</sup>This not possible with many of the built-in types, such as\n", + "> `list` and `dict` objects, nor with types that are defined in Python\n", + "> extensions (Python modules that are written in C).\n", + "\n", + "\n", + "__But...__ while attributes can be added to a Python object at any time, it is\n", + "good practice (and makes for more readable and maintainable code) to add all\n", + "of an object's attributes within the `__init__` method.\n", + "\n", + "\n", + "## Methods\n", + "\n", + "\n", + "We've been dilly-dallying on this little `FSLMaths` project for a while now,\n", + "but our class still can't actually do anything. Let's start adding some\n", + "functionality:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class FSLMaths(object):\n", + " \"\"\"This class is the Python version of the fslmaths shell command. \"\"\"\n", + "\n", + " def __init__(self, inimg):\n", + " \"\"\"Create an FSLMaths object, which will work on the specified input\n", + " image.\n", + " \"\"\"\n", + " self.input = inimg\n", + " self.operations = []\n", + "\n", + " def add(self, value):\n", + " \"\"\"Add the specified value to the current image. \"\"\"\n", + " self.operations.append(('add', value))\n", + "\n", + " def mul(self, value):\n", + " \"\"\"Multiply the current image by the specified value. \"\"\"\n", + " self.operations.append(('mul', value))\n", + "\n", + " def div(self, value):\n", + " \"\"\"Divide the current image by the specified value. \"\"\"\n", + " self.operations.append(('div', value))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Woah woah, [slow down egg-head, you're going a mile a\n", + "minute!](https://www.youtube.com/watch?v=yz-TemWooa4). We've modified\n", + "`__init__` so that a second attribute called `operations` is added to our\n", + "object - this `operations` attribute is simply a list.\n", + "\n", + "\n", + "Then, we added a handful of methods - `add`, `mul`, and `div` - which each\n", + "append a tuple to that `operations` list.\n", + "\n", + "\n", + "> Note that, just like in the `__init__` method, the first argument that will\n", + "> be passed to the `add` method is `self` - a reference to the object that the\n", + "> method has been called on.\n", + "\n", + "\n", + "The idea behind this design is that our `FSLMaths` class will not actually do\n", + "anything when we call the `add`, `mul` or `div` methods. Instead, it will\n", + "\"stage\" each operation, and then perform them all in one go. So let's add\n", + "another method, `run`, which actually does the work:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import nibabel as nib\n", + "\n", + "\n", + "class FSLMaths(object):\n", + " \"\"\"This class is the Python version of the fslmaths shell command. \"\"\"\n", + "\n", + "\n", + " def __init__(self, inimg):\n", + " \"\"\"Create an FSLMaths object, which will work on the specified input\n", + " image.\n", + " \"\"\"\n", + " self.input = inimg\n", + " self.operations = []\n", + "\n", + "\n", + " def add(self, value):\n", + " \"\"\"Add the specified value to the current image. \"\"\"\n", + " self.operations.append(('add', value))\n", + "\n", + "\n", + " def mul(self, value):\n", + " \"\"\"Multiply the current image by the specified value. \"\"\"\n", + " self.operations.append(('mul', value))\n", + "\n", + "\n", + " def div(self, value):\n", + " \"\"\"Divide the current image by the specified value. \"\"\"\n", + " self.operations.append(('div', value))\n", + "\n", + "\n", + " def run(self, output=None):\n", + " \"\"\"Apply all staged operations, and return the final result, or\n", + " save it to the specified output file.\n", + " \"\"\"\n", + "\n", + " data = self.input.get_data()\n", + "\n", + " for operation, value in self.operations:\n", + "\n", + " # if value is a string, we assume that\n", + " # it is a path to an image. Otherwise,\n", + " # we assume that it is a scalar value.\n", + " if isinstance(image, str):\n", + " image = nib.load(value)\n", + " value = image.get_data()\n", + "\n", + " if operation == 'add':\n", + " data = data + value\n", + " elif operation == 'mul':\n", + " data = data * value\n", + " elif operation == 'div':\n", + " data = data / value\n", + "\n", + " # turn final output into a nifti,\n", + " # and save it to disk if an\n", + " # 'output' has been specified.\n", + " outimg = nib.nifti1.Nifti1Image(data, inimg.affine)\n", + "\n", + " if output is not None:\n", + " nib.save(outimg, output)\n", + "\n", + " return outimg" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now have a useable (but not very useful) `FSLMaths` class!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "input = op.expandvars('$FSLDIR/data/standard/MNI152_T1_2mm.nii.gz')\n", + "inimg = nib.load(input)\n", + "fm = FSLMaths(inimg)\n", + "fm.mul(op.expandvars('$FSLDIR/data/standard/MNI152_T1_2mm_brain_mask.nii.gz')\n", + "fm.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Appendix: The `object` base-class\n", + "\n", + "In older code bases, you might see class definitions that look like this,\n", + "without explicitly inheriting from the `object` base class:\n", + "\n", + "> ```\n", + "> class MyClass:\n", + "> ...\n", + "> ```\n", + "\n", + "This syntax is a [throwback to older versions of\n", + "Python](https://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes)\n", + "- in Python 3 there is actually no difference between defining a class in this\n", + "way, and defining a class in the way we have shown in this tutorial.\n", + "\n", + "But if you are writing code which needs to run on both Python 2 and 3, you\n", + "_must_ define your classes to explicitly inherit from the `object` base class.\n", + "\n", + "\n", + "## Appendix: `__init__` versus `__new__`\n", + "\n", + "\n", + "In Python, object creation is actually a two-stage process - _creation_, and\n", + "then _initialisation_. The `__init__` method gets called during the\n", + "_initialisation_ stage - its job is to initialise the state of the object. But\n", + "note that, by the time `__init__` gets called, the object has already been\n", + "created.\n", + "\n", + "\n", + "You can also define a method called `__new__` if you need to control the\n", + "creation stage, although this is very rarely needed. A brief explanation on\n", + "the difference between `__new__` and `__init__` can be found\n", + "[here](https://www.reddit.com/r/learnpython/comments/2s3pms/what_is_the_difference_between_init_and_new/cnm186z/),\n", + "and you may also wish to take a look at the [official Python\n", + "docs](https://docs.python.org/3.5/reference/datamodel.html#basic-customization)." + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/advanced_topics/object_oriented_programming.md b/advanced_topics/object_oriented_programming.md new file mode 100644 index 0000000000000000000000000000000000000000..69280826ca19975769efbb6e3c61bcb6a297e2a9 --- /dev/null +++ b/advanced_topics/object_oriented_programming.md @@ -0,0 +1,413 @@ +# Object-oriented programming + + +By now you might have realised that __everything__ in Python is an +object. Strings are objects, numbers are objects, functions are objects, +modules are objects - __everything__ is an object! + + +But this does not mean that you have to use Python in an object-oriented +fashion. You can stick with functions and statements, and get quite a lot +done. But some problems are just easier to solve, and to reason about, when +you use an object-oriented approach. + + +## Objects versus classes + + +If you are versed in C++, Java, C#, or some other object-oriented language, +then this should all hopefully sound familiar, and you can skip to the next +section. + + +If you have not done any object-oriented programming before, your first step +is to understand the difference between _objects_ (also known as _instances_) +and _classes_ (also known as _types_). + + +If you have some experience in C, then you can start off by thinking of a +class as like a `struct` definition - it is a specification for the layout of +a chunk of memory. For example, here is a typical struct definition: + +> ``` +> /** +> * Struct representing a stack. +> */ +> typedef struct __stack { +> uint8_t capacity; /**< the maximum capacity of this stack */ +> uint8_t size; /**< the current size of this stack */ +> void **top; /**< pointer to the top of this stack */ +> } stack_t; +> ``` + + +Now, an _object_ is not a definition, but rather a thing which resides in +memory. An object can have _attributes_ (pieces of information), and _methods_ +(functions associated with the object). You can pass objects around your code, +manipulate their attributes, and call their methods. + + +Returning to our C metaphor, you can think of an object as like an +instantiation of a struct: + + +> ``` +> stack_t stack; +> stack.capacity = 10; +> ``` + + +The fundamental difference between a `struct` in C, and a `class` in Python +and other object oriented languages, is that you can't (easily) add functions +to a `struct` - it is just a chunk of memory. Whereas in Python, you can add +functions to your class definition, which will then be added as methods when +you create an object from that class. + + +Of course there are many more differences between C structs and classes (most +notably [inheritance](todo), and [protection](todo)). But if you can +understand the difference between a _definition_ of a C struct, and an +_instantiation_ of that struct, then you are most of the way towards +understanding the difference between a Python _class_, and a Python _object_. + + +> But just to confuse you, remember that in Python, __everything__ is an +> object - even classes! + + +## Defining a class + + +Defining a class in Python is simple. Let's take on a small project, by +developing a class which can be used in place of the `fslmaths` shell command. + + +``` +class FSLMaths(object): + pass +``` + + +In this statement, we defined a new class called `FSLMaths`, which inherits +from the built-in `object` base-class (see [below](todo) for more details on +inheritance). + + +Now that we have defined our class, we can create objects - instances of that +class - by calling the class itself, as if it were a function: + + +``` +fm1 = FSLMaths() +fm2 = FSLMaths() +print(fm1) +print(fm2) +``` + + +Although these objects are not of much use at this stage. Let's do some more +work. + + +## Object creation - the `__init__` method + + +The first thing that our `fslmaths` replacement will need is an input image. +It makes sense to pass this in when we create an `FSLMaths` object: + + +``` +class FSLMaths(object): + def __init__(self, inimg): + self.input = inimg +``` + + +Here we have added a _method_ called `__init__` to our class (remember that a +_method_ is just a function which is associated with a specific object). This +method expects two arguments - `self`, and `inimg`. So now, when we create an +instance of the `FSLMaths` class, we will need to provide an input image: + + +``` +import nibabel as nib +import os.path as op + +input = op.expandvars('$FSLDIR/data/standard/MNI152_T1_2mm.nii.gz') +inimg = nib.load(input) +fm = FSLMaths(inimg) +``` + + +There are a couple of things to note here: + + +__Our method is called__ `__init__`__, but we didn't actually call the__ +`__init__` __method!__ `__init__` is a special method in Python - it is called +when an instance of a class is created. And recall that we can create an +instance of a class by calling the class in the same way that we call a +function. + + +__We didn't specify the `self` argument - what gives?!?__ The `self` argument +is a special argument that is specific to methods in Python. If you are coming +from C++, Java, C# or similar, `self` in Python is equivalent to `this` in +those languages. + + +### The `self` argument + + +In a method, the `self` argument is a reference to the object that the method +was called on. So in this line of code: + + +``` +fm = FSLMaths(inimg) +``` + + +the `self` argument in `__init__` will be a reference to the `FSLMaths` object +that has been created (and is then assigned to the `fm` variable, after the +`__init__` method has finished). + + +But note that we do not need to provide the `self` argument - this is a quirk +specific to methods in Python - when you call a method on an object (or a +class, to create a new object), the Python runtime will take care of passing +the instance as the `self` argument to to the method. + + +But when you are writing a class, you _do_ need to explicitly list `self` as +the first argument to all of the methods of the class. + + +## Attributes + + +In Python, the term _attribute_ is used to refer to a piece of information +that is associated with an object. An attribute is generally a reference to +another object (which might be a string, a number, or a list, or some other +more complicated object). + + +Remember that we modified our `FSLMaths` class so that it is passed an input +image on creation: + + +``` +class FSLMaths(object): + def __init__(self, inimg): + self.input = inimg + +input = op.expanduser('$FSLDIR/data/standard/MNI152_T1_2mm.nii.gz') +fm = FSLMaths(nib.load(input)) +``` + + +Take a look at what is going on in the `__init__` method - we take the `inimg` +argument, and create a reference to it called `self.input`. We have added an +_attribute_ to the `FSLMaths` instance, called `input`, and we can access that +attribute like so: + + +``` +print('Input for our FSLMaths instance: {}'.format(fm.input)) +``` + + +And that concludes the section on adding attributes to Python objects. + + +Just kidding. But it really is that simple. This is one aspect of Python which +might be quite jarring to you if you are coming from a language with more +rigid semantics, such as C++ or Java. In those languages, you must pre-specify +all of the attributes and methods that are a part of a class. But Python works +a bit differently - you add attributes to an object affer it has been created. +In fact, you can even do this outside of the class definition<sup>1</sup>: + + +``` +fm = FSLMaths(inimg) +fm.another_attribute = 'Haha' +print(fm.another_attribute) +``` + + +> <sup>1</sup>This not possible with many of the built-in types, such as +> `list` and `dict` objects, nor with types that are defined in Python +> extensions (Python modules that are written in C). + + +__But...__ while attributes can be added to a Python object at any time, it is +good practice (and makes for more readable and maintainable code) to add all +of an object's attributes within the `__init__` method. + + +## Methods + + +We've been dilly-dallying on this little `FSLMaths` project for a while now, +but our class still can't actually do anything. Let's start adding some +functionality: + + +``` +class FSLMaths(object): + """This class is the Python version of the fslmaths shell command. """ + + def __init__(self, inimg): + """Create an FSLMaths object, which will work on the specified input + image. + """ + self.input = inimg + self.operations = [] + + def add(self, value): + """Add the specified value to the current image. """ + self.operations.append(('add', value)) + + def mul(self, value): + """Multiply the current image by the specified value. """ + self.operations.append(('mul', value)) + + def div(self, value): + """Divide the current image by the specified value. """ + self.operations.append(('div', value)) +``` + + +Woah woah, [slow down egg-head, you're going a mile a +minute!](https://www.youtube.com/watch?v=yz-TemWooa4). We've modified +`__init__` so that a second attribute called `operations` is added to our +object - this `operations` attribute is simply a list. + + +Then, we added a handful of methods - `add`, `mul`, and `div` - which each +append a tuple to that `operations` list. + + +> Note that, just like in the `__init__` method, the first argument that will +> be passed to the `add` method is `self` - a reference to the object that the +> method has been called on. + + +The idea behind this design is that our `FSLMaths` class will not actually do +anything when we call the `add`, `mul` or `div` methods. Instead, it will +"stage" each operation, and then perform them all in one go. So let's add +another method, `run`, which actually does the work: + + +``` +import nibabel as nib + + +class FSLMaths(object): + """This class is the Python version of the fslmaths shell command. """ + + + def __init__(self, inimg): + """Create an FSLMaths object, which will work on the specified input + image. + """ + self.input = inimg + self.operations = [] + + + def add(self, value): + """Add the specified value to the current image. """ + self.operations.append(('add', value)) + + + def mul(self, value): + """Multiply the current image by the specified value. """ + self.operations.append(('mul', value)) + + + def div(self, value): + """Divide the current image by the specified value. """ + self.operations.append(('div', value)) + + + def run(self, output=None): + """Apply all staged operations, and return the final result, or + save it to the specified output file. + """ + + data = self.input.get_data() + + for operation, value in self.operations: + + # if value is a string, we assume that + # it is a path to an image. Otherwise, + # we assume that it is a scalar value. + if isinstance(image, str): + image = nib.load(value) + value = image.get_data() + + if operation == 'add': + data = data + value + elif operation == 'mul': + data = data * value + elif operation == 'div': + data = data / value + + # turn final output into a nifti, + # and save it to disk if an + # 'output' has been specified. + outimg = nib.nifti1.Nifti1Image(data, inimg.affine) + + if output is not None: + nib.save(outimg, output) + + return outimg +``` + + +We now have a useable (but not very useful) `FSLMaths` class! + + +``` +input = op.expandvars('$FSLDIR/data/standard/MNI152_T1_2mm.nii.gz') +inimg = nib.load(input) +fm = FSLMaths(inimg) +fm.mul(op.expandvars('$FSLDIR/data/standard/MNI152_T1_2mm_brain_mask.nii.gz') +fm.run() +``` + + +## Appendix: The `object` base-class + +In older code bases, you might see class definitions that look like this, +without explicitly inheriting from the `object` base class: + +> ``` +> class MyClass: +> ... +> ``` + +This syntax is a [throwback to older versions of +Python](https://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes) +- in Python 3 there is actually no difference between defining a class in this +way, and defining a class in the way we have shown in this tutorial. + +But if you are writing code which needs to run on both Python 2 and 3, you +_must_ define your classes to explicitly inherit from the `object` base class. + + +## Appendix: `__init__` versus `__new__` + + +In Python, object creation is actually a two-stage process - _creation_, and +then _initialisation_. The `__init__` method gets called during the +_initialisation_ stage - its job is to initialise the state of the object. But +note that, by the time `__init__` gets called, the object has already been +created. + + +You can also define a method called `__new__` if you need to control the +creation stage, although this is very rarely needed. A brief explanation on +the difference between `__new__` and `__init__` can be found +[here](https://www.reddit.com/r/learnpython/comments/2s3pms/what_is_the_difference_between_init_and_new/cnm186z/), +and you may also wish to take a look at the [official Python +docs](https://docs.python.org/3.5/reference/datamodel.html#basic-customization).