diff --git a/fsl/data/featdesign.py b/fsl/data/featdesign.py
index 263ba244a78e58b49d0c04d82d40d92fdb037c92..640a6cd2ba5bbdb9834a345121c7b39234a50e65 100644
--- a/fsl/data/featdesign.py
+++ b/fsl/data/featdesign.py
@@ -130,16 +130,20 @@ class FEATFSFDesign(object):
     with FSL 5.0.9 and older.
     """
     
-    def __init__(self, featDir, settings):
+    def __init__(self, featDir, settings=None):
         """Create a ``FEATFSFDesign``.
 
         :arg featDir:      Path to the FEAT directory.
 
         :arg settings:     A dictionary containing the FEAT analysis 
-                           settings from its ``design.fsf`` file (see
-                           :func:`.featanalysis.loadSettings`).
+                           settings from its ``design.fsf``. If not provided,
+                           is loaded via :func:`.featanalysis.loadSettings`.
         """
 
+        if settings is None:
+            from .featanalysis import loadSettings
+            settings = loadSettings(featDir)
+
         # Get the design matrix, and some
         # information about the analysis
         designMatrix = loadDesignMat(featDir)
diff --git a/fsl/utils/notifier.py b/fsl/utils/notifier.py
index d9b6a143f9f35352511d62ca1c1b5b0c4943e2db..f26d4141c5718f561e03670fef28afcf1877ec6a 100644
--- a/fsl/utils/notifier.py
+++ b/fsl/utils/notifier.py
@@ -16,9 +16,9 @@ import collections
 
 import six
 
-import props
 
-import fsl.utils.async as async
+import fsl.utils.async       as async
+import fsl.utils.weakfuncref as weakfuncref
 
 
 log = logging.getLogger(__name__)
@@ -41,7 +41,7 @@ class _Listener(object):
 
         # We use a WeakFunctionRef so we can refer to
         # both functions and class/instance methods
-        self.__callback = props.WeakFunctionRef(callback)
+        self.__callback = weakfuncref.WeakFunctionRef(callback)
         self.topic      = topic
         self.runOnIdle  = runOnIdle
         self.enabled    = True
@@ -105,10 +105,6 @@ class Notifier(object):
         # { topic : enabled } mappings.
         new.__enabled = {}
 
-        if isinstance(new, props.HasProperties):
-            log.warning('Warning: {} is a sub-class of both '
-                        'Notifier and props.HasProperties!')
-
         return new
 
         
diff --git a/fsl/utils/typedict.py b/fsl/utils/typedict.py
index 0ac4cdcde3c3f2853fcf645e866e6641ae0b7c1a..f3b9510bc672f08865ba7484a67579c3e534a285 100644
--- a/fsl/utils/typedict.py
+++ b/fsl/utils/typedict.py
@@ -28,12 +28,11 @@ class TypeDict(object):
     
     Let's say we have a class with some properties::
 
-        import props
         import fsl.utils.typedict as td
 
-        class Animal(props.HasProperties):
-            isMammal = props.Boolean()
-            numLegs  = props.Int()
+        class Animal(object):
+            isMammal = True
+            numLegs  = 4
 
     
     And we want to associate some tooltips with those properties::
@@ -63,7 +62,7 @@ class TypeDict(object):
     This functionality also works across class hierarchies::
 
         class Cat(Animal):
-           numYoutubeHits = props.Int()
+           numYoutubeHits = 10
 
         tooltips = td.TypeDict({
 
diff --git a/fsl/utils/weakfuncref.py b/fsl/utils/weakfuncref.py
new file mode 100644
index 0000000000000000000000000000000000000000..8cc4b91813a136a90ad402944b24e1b0f91340c1
--- /dev/null
+++ b/fsl/utils/weakfuncref.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+#
+# weakfuncref.py - The WeakFunctionRef class
+#
+# Author: Paul McCarthy <pauldmccarthy@gmail.com>
+#
+"""This module provides the :class:`WeakFunctionRef` class. """
+
+
+import six
+import types
+import weakref
+import inspect
+
+
+class WeakFunctionRef(object):
+    """Class which encapsulates a :mod:`weakref` to a function or method.
+
+    This class is used by :class:`.Notifier` instances to reference
+    listeners which have been registered to be notified of property value
+    or attribute changes.
+    """
+
+    
+    def __init__(self, func):
+        """Create a new ``WeakFunctionRef`` to encapsulate the given
+        function or bound/unbound method.
+        """
+
+        # Bound method
+        if self.__isMethod(func):
+
+            boundMeth = six.get_method_function(func)
+            boundSelf = six.get_method_self(    func)
+
+            # We can't take a weakref of the method
+            # object, so we have to weakref the object
+            # and the unbound class function. The
+            # function method will search for and
+            # return the bound method, though.
+            self.obj  = weakref.ref(boundSelf)
+            self.func = weakref.ref(boundMeth)
+
+            self.objType  = type(boundSelf).__name__
+            self.funcName =      boundMeth .__name__
+
+        # Unbound/class method or function
+        else:
+ 
+            self.obj      = None
+            self.objType  = None
+            self.func     = weakref.ref(func)
+            self.funcName = func.__name__
+
+
+    def __str__(self):
+        """Return a string representation of the function."""
+
+        selftype = type(self).__name__
+        func     = self.function()
+
+        if self.obj is None:
+            s = '{}: {}'   .format(selftype, self.funcName)
+        else:
+            s = '{}: {}.{}'.format(selftype, self.objType, self.funcName)
+
+        if func is None: return '{} <dead>'.format(s)
+        else:            return s
+
+        
+    def __repr__(self):
+        """Return a string representation of the function."""
+        return self.__str__()
+
+    
+    def __isMethod(self, func):
+        """Returns ``True`` if the given function is a bound method,
+        ``False`` otherwise.
+        
+        This seems to be one of the few areas where python 2 and 3 are
+        irreconcilably incompatible (or just where :mod:`six` does not have a
+        function to help us).
+        
+        In Python 2 there is no difference between an unbound method and a
+        function. But in Python 3, an unbound method is still a method (and
+        inspect.ismethod returns True).
+        """
+
+        ismethod = False
+
+        # Therefore, in python2 we need to test
+        # whether the function is a method, and
+        # also test whether it is bound.
+        if six.PY2:
+            ismethod = (inspect.ismethod(func) and
+                        six.get_method_self(func) is not None)
+
+        # But in python3, if the function is a
+        # method it is, by definition, bound.
+        elif six.PY3:
+            ismethod = inspect.ismethod(func)
+
+        return ismethod
+    
+
+    def __findPrivateMethod(self):
+        """Finds and returns the bound method associated with the encapsulated
+        function.
+        """
+
+        obj      = self.obj()
+        func     = self.func()
+        methName = self.funcName
+
+        # Find all attributes on the object which end with
+        # the method name - there will be more than one of
+        # these if the object has base classes which have
+        # private methods of the same name.
+        attNames = dir(obj)
+        attNames = [a for a in attNames if a.endswith(methName)]
+
+        # Find the attribute with the correct name, which
+        # is a method, and has the correct function.
+        for name in attNames:
+
+            att = getattr(obj, name)
+
+            if isinstance(att, types.MethodType) and \
+               six.get_method_function(att) is func:
+                return att
+
+        return None
+
+    
+    def function(self):
+        """Return a reference to the encapsulated function or method,
+        or ``None`` if the function has been garbage collected.
+        """
+
+        # Unbound/class method or function
+        if self.obj is None:
+            return self.func()
+
+        # The instance owning the method has been destroyed
+        if self.obj() is None or self.func() is None:
+            return None
+
+        obj = self.obj()
+
+        # Return the bound method object
+        try:    return getattr(obj, self.funcName)
+
+        # If the function is a bound private method,
+        # its name on the instance will have been
+        # mangled, so we need to search for it 
+        except: return self.__findPrivateMethod()