diff --git a/nibabel/analyze.py b/nibabel/analyze.py
index e870a56170..fe64df696d 100644
--- a/nibabel/analyze.py
+++ b/nibabel/analyze.py
@@ -800,6 +800,22 @@ def scaling_from_data(self, data):
             out_dtype,
             self.has_data_intercept)
 
+    def state_stamper(self, caller):
+        """ Return stamp for current state of `self`
+
+        Parameters
+        ----------
+        caller : callable
+            May be object from which this method was called.  Not used by
+            analyze headers, but may be used by subclasses
+
+        Returns
+        -------
+        stamp : object
+            object unique to this state of `self`
+        """
+        return self.__class__, self.binaryblock
+
     @classmethod
     def _get_checks(klass):
         ''' Return sequence of check functions for this class '''
@@ -899,18 +915,7 @@ class AnalyzeImage(SpatialImage):
     files_types = (('image','.img'), ('header','.hdr'))
     _compressed_exts = ('.gz', '.bz2')
 
-    class ImageArrayProxy(ArrayProxy):
-        ''' Analyze-type implemention of array proxy protocol
-
-        The array proxy allows us to freeze the passed fileobj and
-        header such that it returns the expected data array.
-        '''
-        def _read_data(self):
-            fileobj = allopen(self.file_like)
-            data = self.header.data_from_fileobj(fileobj)
-            if isinstance(self.file_like, basestring): # filename
-                fileobj.close()
-            return data
+    ImageArrayProxy = ArrayProxy
 
     def get_header(self):
         ''' Return header
@@ -941,9 +946,7 @@ def from_file_map(klass, file_map):
         img = klass(data, None, header, file_map=file_map)
         # set affine from header though
         img._affine = header.get_best_affine()
-        img._load_cache = {'header': hdr_copy,
-                           'affine': img._affine.copy(),
-                           'file_map': copy_file_map(file_map)}
+        img._stored_state['affine'] = img.stamper(img._affine)
         return img
 
     @staticmethod
diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py
index e9162542fc..baf59c5fc7 100644
--- a/nibabel/arrayproxy.py
+++ b/nibabel/arrayproxy.py
@@ -6,14 +6,48 @@
 #   copyright and license terms.
 #
 ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
-''' Array proxy base class '''
+""" Array proxy base class
+
+The API is - at minimum:
+
+* The object has an attribute ``shape``
+* that the object returns the data array from ``np.asarray(obj)``
+* that modifying no object outside ``obj`` will affect the result of
+  ``np.asarray(obj)``.  Specifically, if you pass a header into the the
+  __init__, then modifying the original header will not affect the result of the
+  array return.
+
+You might also want to implement ``state_stamper``
+"""
+
+from .volumeutils import allopen
+
 
 class ArrayProxy(object):
+    """
+    The array proxy allows us to freeze the passed fileobj and header such that
+    it returns the expected data array.
+
+    This fairly generic implementation allows us to deal with Analyze and its
+    variants, including Nifti1, and with the MGH format, apparently.
+
+    It requires a ``header`` object with methods:
+    * copy
+    * get_data_shape
+    * data_from_fileobj
+
+    Other image types might need to implement their own implementation of this
+    API.  See :mod:`minc` for an example.
+    """
     def __init__(self, file_like, header):
         self.file_like = file_like
         self.header = header.copy()
         self._data = None
-        self.shape = header.get_data_shape()
+        self._shape = header.get_data_shape()
+
+    @property
+    def shape(self):
+        return self._shape
 
     def __array__(self):
         ''' Cached read of data from file '''
@@ -22,6 +56,41 @@ def __array__(self):
         return self._data
 
     def _read_data(self):
-        raise NotImplementedError
+        fileobj = allopen(self.file_like)
+        data = self.header.data_from_fileobj(fileobj)
+        if isinstance(self.file_like, basestring):  # filename
+            fileobj.close()
+        return data
+
+    def state_stamper(self, caller):
+        """ Return stamp for current state of `self`
+
+        The result somewhat uniquely identifies the state of the array proxy.
+        It assumes that the underlying ``self.file_like`` does not get modified.
+        Specifically, if you open a file-like object, pass into an arrayproxy
+        (call it ``ap``) and get the stamp (say with ``Stamper()(ap)``, then
+        this stamp will uniquely identify the result of ``np.asarry(ap)`` only
+        if the file-like object has not changed.
+
+        Parameters
+        ----------
+        caller : callable
+            callable object from which this method was called.
 
+        Returns
+        -------
+        stamp : object
+            object unique to this state of `self`
 
+        Notes
+        -----
+        The stamp changes if the array to be returned has been cached
+        (``_data`` attribute). This is because this makes it possible to change
+        the array outside the proxy object, because further calls to
+        ``__array__`` returns a refernce to ``self._data``, and the reference
+        allows the caller to modify the array in-place.
+        """
+        return (self.__class__,
+                self.file_like,
+                caller(self.header),
+                caller(self._data))
diff --git a/nibabel/fileholders.py b/nibabel/fileholders.py
index 9b46c40c68..64a0a6c65c 100644
--- a/nibabel/fileholders.py
+++ b/nibabel/fileholders.py
@@ -90,6 +90,30 @@ def same_file_as(self, other):
         return ((self.filename == other.filename) and
                 (self.fileobj == other.fileobj))
 
+    def state_stamper(self, caller):
+        """ Get record of state of fileholder
+
+        See: :mod:`stampers`
+
+        Parameters
+        ----------
+        caller : object
+            Passed from stamper object, but not used by us
+
+        Returns
+        -------
+        stamp : tuple
+            state stamp
+
+        Notes
+        -----
+        We can get state stamp for these file objects assuming that the same
+        filename corresponds to the same file.  We can let pass the position of
+        reading in the file because we are recording the position with
+        ``self.pos``.
+        """
+        return (self.filename, self.fileobj, self.pos)
+
 
 def copy_file_map(file_map):
     ''' Copy mapping of fileholders given by `file_map`
@@ -109,4 +133,3 @@ def copy_file_map(file_map):
     for key, fh in file_map.items():
         fm_copy[key] = copy(fh)
     return fm_copy
-    
diff --git a/nibabel/freesurfer/mghformat.py b/nibabel/freesurfer/mghformat.py
index ab16efa307..d916d78ea4 100644
--- a/nibabel/freesurfer/mghformat.py
+++ b/nibabel/freesurfer/mghformat.py
@@ -429,18 +429,7 @@ class MGHImage(SpatialImage):
     files_types = (('image', '.mgh'),)
     _compressed_exts = ('.mgz',)
 
-    class ImageArrayProxy(ArrayProxy):
-        ''' Analyze-type implemention of array proxy protocol
-
-        The array proxy allows us to freeze the passed fileobj and
-        header such that it returns the expected data array.
-        '''
-        def _read_data(self):
-            fileobj = allopen(self.file_like)
-            data = self.header.data_from_fileobj(fileobj)
-            if isinstance(self.file_like, basestring):  # filename
-                fileobj.close()
-            return data
+    ImageArrayProxy = ArrayProxy
 
     def get_header(self):
         ''' Return the MGH header given the MGHImage'''
@@ -483,9 +472,6 @@ def from_file_map(klass, file_map):
         hdr_copy = header.copy()
         data = klass.ImageArrayProxy(mghf, hdr_copy)
         img = klass(data, affine, header, file_map=file_map)
-        img._load_cache = {'header': hdr_copy,
-                           'affine': affine.copy(),
-                           'file_map': copy_file_map(file_map)}
         return img
 
     def to_file_map(self, file_map=None):
diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py
index bea84da9cf..27bcea3d3d 100644
--- a/nibabel/nifti1.py
+++ b/nibabel/nifti1.py
@@ -502,6 +502,21 @@ def from_fileobj(klass, fileobj, size, byteswap):
             extensions.append(ext)
         return extensions
 
+    def state_stamper(self, caller):
+        """ Return stamp for current state of `self`
+
+        Parameters
+        ----------
+        caller : callable
+            callable with which we can process our state
+
+        Returns
+        -------
+        stamp : object
+            object unique to this state of `self`
+        """
+        return self.__class__, caller(list(self))
+
 
 class Nifti1Header(SpmAnalyzeHeader):
     ''' Class for NIFTI1 header
@@ -620,6 +635,21 @@ def default_structarr(klass, endianness=None):
             hdr_data['vox_offset'] = 0
         return hdr_data
 
+    def state_stamper(self, caller):
+        """ Return stamp for current state of `self`
+
+        Parameters
+        ----------
+        caller : None or callable
+            May be object from which this method was called.
+
+        Returns
+        -------
+        stamp : object
+            object unique to this state of `self`
+        """
+        return self.__class__, self.binaryblock, caller(self.extensions)
+
     def get_qform_quaternion(self):
         ''' Compute quaternion from b, c, d of quaternion
 
diff --git a/nibabel/spatialimages.py b/nibabel/spatialimages.py
index a5c5755376..b5c577c27d 100644
--- a/nibabel/spatialimages.py
+++ b/nibabel/spatialimages.py
@@ -130,6 +130,7 @@
 
 import numpy as np
 
+from .stampers import NdaStamper
 from .filename_parser import types_filenames, TypesFilenamesError
 from .fileholders import FileHolder
 from .volumeutils import shape_zoom_affine
@@ -164,7 +165,7 @@ def from_header(klass, header=None):
         if header is None:
             return klass()
         # I can't do isinstance here because it is not necessarily true
-        # that a subclass has exactly the same interface as it's parent
+        # that a subclass has exactly the same interface as its parent
         # - for example Nifti1Images inherit from Analyze, but have
         # different field names
         if type(header) == klass:
@@ -254,6 +255,24 @@ def data_from_fileobj(self, fileobj):
         data_bytes = fileobj.read(data_size)
         return np.ndarray(shape, dtype, data_bytes, order='F')
 
+    def state_stamper(self, caller):
+        """ Return stamp for current state of `self`
+
+        Parameters
+        ----------
+        caller : callable
+            May be object from which this method was called.
+
+        Returns
+        -------
+        stamp : object
+            object unique to this state of `self`
+        """
+        return (self.__class__,
+                np.dtype(self._dtype),
+                tuple(self._shape),
+                tuple(self._zooms))
+
 
 class ImageDataError(Exception):
     pass
@@ -266,6 +285,8 @@ class ImageFileError(Exception):
 class SpatialImage(object):
     header_class = Header
     files_types = (('image', None),)
+    # Object with which to get state stamps for components
+    stamper = NdaStamper()
     _compressed_exts = ()
 
     ''' Template class for images '''
@@ -319,7 +340,7 @@ def __init__(self, data, affine, header=None,
         if file_map is None:
             file_map = self.__class__.make_file_map()
         self.file_map = file_map
-        self._load_cache = None
+        self._stored_state = self.current_state()
 
     def update_header(self):
         ''' Update header from information in image'''
@@ -558,3 +579,74 @@ def from_image(klass, img):
                      klass.header_class.from_header(img.get_header()),
                      extra=img.extra.copy())
 
+    def current_state(self, stamper=None):
+        """ Return dictionary unique to current state of the image
+
+        The state of an image is defined by all of:
+        * data
+        * affine
+        * header
+        * file_map
+
+        Note we ignore ``extra`` in defining state.
+
+        Parameters
+        ----------
+        stamper : None or callable
+            Object with which to create state stamps for components of image.
+            Defaults to an ndarray-aware stamper
+
+        Returns
+        -------
+        state : dict
+            dictionary with key, value pairs for each image component
+        """
+        if stamper is None:
+            stamper = self.stamper
+        return dict(data=stamper(self._data),
+                    affine=stamper(self._affine),
+                    header=stamper(self._header),
+                    file_map=stamper(self.file_map))
+
+    def reset_changed(self):
+        """ Reset stored state so that changes are relative to current state
+
+        Checkpoints image stored state so that ``maybe_changed`` is relative to
+        state as of this call.  See ``maybe_changed``.
+        """
+        self._stored_state = self.current_state()
+
+    def maybe_changed(self):
+        """ True if image might have changed relative to last checkpoint
+
+        We record the image state when you create the image object, and when you
+        call ``reset_changed`` explicitly.  We return True from this method if
+        the recorded image state may be different from the current image state.
+        We also return True if the image state is too time-consuming to
+        calculate.
+        """
+        return self._stored_state != self.current_state()
+
+    def state_stamper(self, caller):
+        """ Return stamp for current state of `self`
+
+        The state of an image is defined by all of:
+        * data
+        * affine
+        * header
+        * file_map
+
+        Note we ignore ``extra`` in defining state.
+
+        Parameters
+        ----------
+        caller : callable
+            May be object from which this method was called.
+
+        Returns
+        -------
+        stamp : object
+            object unique to this state of `self`
+        """
+        cstate = self.current_state(caller)
+        return self.__class__, tuple(cstate.items())
diff --git a/nibabel/spm99analyze.py b/nibabel/spm99analyze.py
index e3cd179fcf..0b6e0b74ed 100644
--- a/nibabel/spm99analyze.py
+++ b/nibabel/spm99analyze.py
@@ -279,6 +279,8 @@ def from_file_map(klass, file_map):
         to_111 = np.eye(4)
         to_111[:3,3] = 1
         ret._affine = np.dot(ret._affine, to_111)
+        # Update stored affine stamp
+        ret._stored_state['affine'] = ret.stamper(ret._affine)
         return ret
 
     def to_file_map(self, file_map=None):
diff --git a/nibabel/stampers.py b/nibabel/stampers.py
new file mode 100644
index 0000000000..7160517e00
--- /dev/null
+++ b/nibabel/stampers.py
@@ -0,0 +1,253 @@
+""" State stamps
+
+A state stamp is something that defines the state of an object. Let's say we have
+an object ``X`` is in a state $S$.
+
+Let's call the state stamp finder ``get_state_stamp``.  This could be the result
+of ``get_state_stamp = Stamper()`` below, for example.
+
+The *state stamp* of ``X`` is some value ``g`` such that ``get_state_stamp(X) ==
+g`` if and only if ``X`` is in state $S$ - however defined.
+
+``get_state_stamp(Y) == g`` should in general not be true if ``Y`` is a
+different class for ``X``.
+
+Thus the state stamp guarantees a particular state of ``X`` - as defined by you,
+dear programmer. Conversely, if ``get_state_stamp(X) != g`` this does not
+guarantee they are different.  It may be that you (dear programmer) don't know
+if they are different, and do not want to spend resources on working it out.
+For example, if ``X`` is a huge array, you might want to return the
+``Unknown()`` state stamp.
+
+The state stamp ``Unknown()`` is the state stamp such that ``get_state_stamp(X) ==
+Unknown()`` is always False.
+
+If you have objects you want compared, you can do one of:
+
+* define a ``state_stamp`` method, taking a single argument ``caller`` which is
+  the callable from which the method has been called. You can then return
+  something which is unique for the states you want to be able to distinguish.
+  Don't forget that (usually) stamps from objects of different types should
+  compare unequal.
+* subclass the ``Stamper`` class, and extend the ``__call__`` method to
+  handle a new object type.  The ``NdaStamper`` class below is an example.
+
+It's up to the object how to do the stamping.  In general, don't test what the
+stamp is, test whether it compares equal in the situations you are expecting, so
+that the object can change it's mind about how it will do the stamping without
+you having to rewrite the tests.
+"""
+
+import hashlib
+
+import numpy as np
+
+from .py3k import bytes, unicode
+
+
+class Unknown(object):
+    """ state stamp that never matches
+
+    Examples
+    --------
+    >>> u = Unknown()
+    >>> u == u
+    False
+    >>> p = Unknown()
+    >>> u == p
+    False
+
+    Notes
+    -----
+    You would think this could be a singleton, but not so, because:
+
+    >>> u = Unknown()
+    >>> (1, u) == (1, u)
+    True
+
+    Why?  Because comparisons within sequences in CPython, use
+    ``PyObject_RichCompareBool`` for the elements. See around line 572 in
+    ``objects/tupleobject.c`` and around line 607 in ``objects/object.c`` in
+    cpython 69528:fecf9e6d7630.  This does an identity check equivalent to ``u
+    is u``; if this passes it does not do a further equality check (``u == u``).
+    For that reason, if you want to make sure nothing matches ``Unknown()``
+    within sequences, you need a fresh instances.
+    """
+    _is_unknown = True
+
+    def __eq__(self, other):
+        return False
+
+    def __ne__(self, other):
+        return True
+
+    def __repr__(self):
+        return 'Unknown()'
+
+
+def is_unknown(obj):
+    """ Return True if `obj` is an Unknown instance
+
+    Examples
+    --------
+    >>> is_unknown(Unknown())
+    True
+    >>> is_unknown(object())
+    False
+    """
+    try:
+        return obj._is_unknown
+    except AttributeError:
+        return False
+
+
+class Stamper(object):
+    r""" Basic state stamp collector
+
+    Instantiate and call on objects to get state stamps
+
+    Examples
+    --------
+    >>> asker = Stamper()
+    >>> asker(1) == asker(1)
+    True
+    >>> asker(1) == asker(2)
+    False
+    >>> asker('a string') == asker('a string')
+    True
+    >>> asker(1.0) == asker(1.0)
+    True
+    >>> asker(1) == asker(1.0) # different types
+    False
+    >>> asker(object()) == asker(object()) # not known -> False
+    False
+
+    List and tuples
+
+    >>> L = [1, 2]
+    >>> asker(L) == asker([1, 2])
+    True
+    >>> L[0] = 3
+    >>> asker(L) == asker([1, 2])
+    False
+    >>> T = (1, 2)
+    >>> asker(T) == asker((1, 2))
+    True
+    >>> asker(T) == asker([1, 2])
+    False
+    >>> asker([1, object()]) == asker([1, object()])
+    False
+
+    If your object implements ``state_stamper``, you can customized the
+    behavior.
+
+    >>> class D(object):
+    ...     def state_stamper(self, cstate):
+    ...          return 28
+    >>> asker(D()) == asker(D())
+    True
+    """
+    def __init__(self, funcs = None):
+        """ Initialize stamper with optional functions ``funcs``
+
+        Parameters
+        ----------
+        funcs : sequence of callables, optional
+            callables that will be called to process otherwise unknown objects.
+            The signature for the callable is ``f(obj, caller)`` where `obj` is
+            the object being stamped, and ``caller`` will be the
+            ``Stamper``-like object from which the function will be called.
+
+        Examples
+        --------
+        >>> st = Stamper()
+        >>> st((1, object())) == st((1, object()))
+        False
+        >>> def func(obj, caller):
+        ...     return type(obj), 28
+        >>> st2 = Stamper((func,))
+        >>> st2((1, object())) == st2((1, object()))
+        True
+        """
+        if funcs is None:
+            funcs = []
+        self.funcs = list(funcs)
+        # In case custom objects want an intermediate store
+        self.call_state = {}
+
+    def __call__(self, obj):
+        r""" Get state stamp for object `obj`
+
+        Parmeters
+        ---------
+        obj : object
+            Object for which to extract state stamp
+
+        Returns
+        -------
+        stamp_state : object
+            state stamp.  This is an object that compares equal to another
+            object in the same `state`
+        """
+        # Reset call state, in case someone wants to use it
+        self.call_state = {}
+        # None passes through
+        if obj is None:
+            return None
+        tobj = type(obj)
+        # Pass through classes before doing method check on instance
+        if tobj == type: # class
+            return type, obj
+        try:
+            return obj.state_stamper(self)
+        except AttributeError:
+            pass
+        # Immutable objects are their own state stamps
+        if tobj in (str, unicode, bytes, int, float):
+            return tobj, obj
+        if tobj is dict:
+            return dict, sorted(self(obj.items()))
+        # Recurse into known sequence types
+        if tobj in (list, tuple):
+            return tobj(self(v) for v in obj)
+        # Try any additional functions we know about
+        for func in self.funcs:
+            res = func(obj, self)
+            if not res is None and not is_unknown(res):
+                return res
+        return Unknown()
+
+
+class NdaStamper(Stamper):
+    r""" Collect state stamps, using byte buffers for smallish ndarrays
+
+    >>> nda_asker = NdaStamper()
+
+    The standard Stamper behavior
+
+    >>> nda_asker(1) == nda_asker(1)
+    True
+
+    Can also deal with small arrays by hashing byte contents:
+
+    >>> arr = np.zeros((3,), dtype=np.int16)
+    >>> nda_asker(arr) == nda_asker(arr)
+    True
+
+    Depending on the threshold for the number of bytes:
+
+    >>> small_asker = NdaStamper(byte_thresh=5)
+    >>> small_asker(arr)
+    Unknown()
+    """
+    def __init__(self, funcs = None, byte_thresh = 2**16):
+        self.byte_thresh = byte_thresh
+        if funcs is None:
+            funcs = []
+        def _proc_array(obj, cstate):
+            if type(obj) is np.ndarray and obj.nbytes <= byte_thresh:
+                return (type(obj),
+                        tuple(obj.shape),
+                        obj.dtype,
+                        hashlib.md5(obj.tostring()).digest())
+        super(NdaStamper, self).__init__(list(funcs) + [_proc_array])
diff --git a/nibabel/tests/test_analyze.py b/nibabel/tests/test_analyze.py
index ab3b11a57d..e8cac798a4 100644
--- a/nibabel/tests/test_analyze.py
+++ b/nibabel/tests/test_analyze.py
@@ -26,6 +26,8 @@
 from ..loadsave import read_img_data
 from .. import imageglobals
 from ..casting import as_int
+from ..stampers import Stamper
+from ..stampers import Stamper, NdaStamper
 
 from numpy.testing import (assert_array_equal,
                            assert_array_almost_equal)
@@ -450,6 +452,18 @@ def test_base_affine(self):
              [ 0.,  0.,  1., -3.],
              [ 0.,  0.,  0.,  1.]])
 
+    def test_state_stamp(self):
+        # Test state stamp is sensitive to state
+        klass = self.header_class
+        hdr1 = klass()
+        hdr2 = klass()
+        stamper = Stamper()
+        assert_equal(stamper(hdr1), stamper(hdr2))
+        hdr1.set_data_shape((3,5,7))
+        assert_not_equal(stamper(hdr1), stamper(hdr2))
+        hdr2.set_data_shape((3,5,7))
+        assert_equal(stamper(hdr1), stamper(hdr2))
+
 
 def test_best_affine():
     hdr = AnalyzeHeader()
@@ -512,6 +526,48 @@ def test_data_code_error():
 class TestAnalyzeImage(tsi.TestSpatialImage):
     image_class = AnalyzeImage
 
+    def test_state_stamper(self):
+        # Extend tests of state stamping
+        super(TestAnalyzeImage, self).test_state_stamper()
+        # Test modifications of header
+        stamper = NdaStamper()
+        img_klass = self.image_class
+        hdr_klass = self.image_class.header_class
+        # The first test we have done in the parent, but just for completeness
+        arr = np.arange(5, dtype=np.int16)
+        aff = np.eye(4)
+        hdr = hdr_klass()
+        hdr.set_data_dtype(arr.dtype)
+        img1 = img_klass(arr, aff, hdr)
+        img2 = img_klass(arr, aff, hdr)
+        assert_equal(img1.current_state(), img2.current_state())
+        assert_equal(stamper(img1), stamper(img2))
+        hdr['descrip'] = asbytes('something')
+        # Doesn't affect original images
+        assert_equal(img1.current_state(), img2.current_state())
+        assert_equal(stamper(img1), stamper(img2))
+        # Does affect new image
+        img3 = img_klass(arr, aff, hdr)
+        assert_not_equal(img1.current_state(), img3.current_state())
+        assert_not_equal(stamper(img1), stamper(img3))
+
+    def test_maybe_changed(self):
+        # Check that changing header signaled in maybe_changed
+        super(TestAnalyzeImage, self).test_maybe_changed()
+        # Test modifications of header
+        img_klass = self.image_class
+        # The first test we have done in the parent, but just for completeness
+        arr = np.arange(5, dtype=np.int16)
+        aff = np.eye(4)
+        # Get an adapted header
+        hdr = img_klass(arr, aff).get_header()
+        img = img_klass(arr, aff, hdr)
+        assert_false(img.maybe_changed())
+        # Changing the header in the image signaled in maybe_change
+        ihdr = img.get_header()
+        ihdr['descrip'] = asbytes('something')
+        assert_true(img.maybe_changed())
+
     def test_data_hdr_cache(self):
         # test the API for loaded images, such that the data returned
         # from img.get_data() is not affected by subsequent changes to
@@ -590,6 +646,28 @@ def test_header_updating(self):
         assert_array_equal(img_back.shape, (3, 2, 4))
 
 
+    def test_load_caching(self):
+        # Check save / load change recording
+        img_klass = self.image_class
+        arr = np.arange(5, dtype=np.int16)
+        aff = np.diag([2.0,3,4,1])
+        img = img_klass(arr, aff)
+        for key in img.file_map:
+            img.file_map[key].fileobj = BytesIO()
+        img.to_file_map()
+        img2 = img.from_file_map(img.file_map)
+        assert_false(img2.maybe_changed())
+        # Save loads data, so changes image
+        img2.to_file_map()
+        assert_true(img2.maybe_changed())
+        # New loading resets change flag
+        img3 = img.from_file_map(img2.file_map)
+        assert_false(img3.maybe_changed())
+        # Loading data makes change impossible to detect
+        data = img3.get_data()
+        assert_true(img3.maybe_changed())
+
+
 def test_unsupported():
     # analyze does not support uint32
     data = np.arange(24, dtype=np.int32).reshape((2,3,4))
diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py
new file mode 100644
index 0000000000..e7b35e6387
--- /dev/null
+++ b/nibabel/tests/test_arrayproxy.py
@@ -0,0 +1,122 @@
+# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
+# vi: set ft=python sts=4 ts=4 sw=4 et:
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+#
+#   See COPYING file distributed along with the NiBabel package for the
+#   copyright and license terms.
+#
+### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
+""" Tests for arrayproxy module
+"""
+from __future__ import with_statement
+
+from copy import deepcopy
+
+from ..py3k import BytesIO, ZEROB, asbytes
+from ..tmpdirs import InTemporaryDirectory
+
+import numpy as np
+
+from ..arrayproxy import ArrayProxy
+from ..nifti1 import Nifti1Header
+from ..stampers import Stamper
+
+from numpy.testing import assert_array_equal, assert_array_almost_equal
+from nose.tools import (assert_true, assert_false, assert_equal,
+                        assert_not_equal, assert_raises)
+
+
+class FunkyHeader(object):
+    def __init__(self, shape):
+        self.shape = shape
+
+    def copy(self):
+        return self.__class__(self.shape[:])
+
+    def get_data_shape(self):
+        return self.shape[:]
+
+    def data_from_fileobj(self, fileobj):
+        return np.arange(np.prod(self.shape)).reshape(self.shape)
+
+
+def test_init():
+    bio = BytesIO()
+    shape = [2,3,4]
+    hdr = FunkyHeader(shape)
+    ap = ArrayProxy(bio, hdr)
+    assert_true(ap.file_like is bio)
+    assert_equal(ap.shape, shape)
+    # shape should be read only
+    assert_raises(AttributeError, setattr, ap, 'shape', shape)
+    # Check there has been a copy of the header
+    assert_false(ap.header is hdr)
+    # Check we can modify the original header without changing the ap version
+    hdr.shape[0] = 6
+    assert_not_equal(ap.shape, shape)
+    # Get the data
+    assert_array_equal(np.asarray(ap), np.arange(24).reshape((2,3,4)))
+
+
+def write_raw_data(arr, hdr, fileobj):
+    hdr.set_data_shape(arr.shape)
+    hdr.set_data_dtype(arr.dtype)
+    fileobj.write(ZEROB * hdr.get_data_offset())
+    fileobj.write(arr.tostring(order='F'))
+    return hdr
+
+
+def test_nifti1_init():
+    bio = BytesIO()
+    shape = (2,3,4)
+    hdr = Nifti1Header()
+    arr = np.arange(24, dtype=np.int16).reshape(shape)
+    write_raw_data(arr, hdr, bio)
+    hdr.set_slope_inter(2, 10)
+    ap = ArrayProxy(bio, hdr)
+    assert_true(ap.file_like == bio)
+    assert_equal(ap.shape, shape)
+    # Check there has been a copy of the header
+    assert_false(ap.header is hdr)
+    # Get the data
+    assert_array_equal(np.asarray(ap), arr * 2.0 + 10)
+    with InTemporaryDirectory():
+        f = open('test.nii', 'wb')
+        write_raw_data(arr, hdr, f)
+        f.close()
+        ap = ArrayProxy('test.nii', hdr)
+        assert_true(ap.file_like == 'test.nii')
+        assert_equal(ap.shape, shape)
+        assert_array_equal(np.asarray(ap), arr * 2.0 + 10)
+
+
+def test_state_stamp():
+    # Stamps
+    bio = BytesIO()
+    shape = (2, 3, 4)
+    hdr = FunkyHeader(shape)
+    ap = ArrayProxy(bio, hdr)
+    stamper = Stamper()
+    # The header is unstampable in this case
+    assert_not_equal(stamper(ap), stamper(ap))
+    # Nifti is stampable
+    hdr = Nifti1Header()
+    ap1 = ArrayProxy(bio, hdr)
+    ap2 = ArrayProxy(bio, hdr)
+    assert_equal(stamper(ap1), stamper(ap2))
+    ap3 = ArrayProxy('afilename', hdr)
+    ap4 = ArrayProxy('afilename', hdr)
+    assert_equal(stamper(ap3), stamper(ap4))
+    assert_not_equal(stamper(ap1), stamper(ap3))
+    # write some data to check arr != proxy
+    arr = np.arange(24, dtype=np.int16).reshape(shape) + 100
+    new_hdr = write_raw_data(arr, hdr, bio)
+    ap5 = ArrayProxy(bio, new_hdr)
+    assert_equal(stamper(ap5), stamper(ArrayProxy(bio, new_hdr)))
+    # Reading the data makes the arrayproxy unstampable, because the data is now
+    # modifiable outside the proxy if we modify the returned array in place.
+    arr_back = np.asanyarray(ap5)
+    assert_not_equal(stamper(ap1), stamper(ap5))
+    # Check that the proxy does not seem to be the same as the array
+    assert_array_equal(arr, arr_back)
+    assert_not_equal(stamper(arr), stamper(ap5))
diff --git a/nibabel/tests/test_fileholders.py b/nibabel/tests/test_fileholders.py
index 9494bd5f28..ff622f9abe 100644
--- a/nibabel/tests/test_fileholders.py
+++ b/nibabel/tests/test_fileholders.py
@@ -1,17 +1,18 @@
 """ Testing fileholders
 """
 
-from StringIO import StringIO
-
-import numpy as np
+from ..py3k import BytesIO
 
 from ..fileholders import FileHolder, FileHolderError, copy_file_map
 from ..tmpdirs import InTemporaryDirectory
+from ..stampers import Stamper
 
 from numpy.testing import (assert_array_almost_equal,
                            assert_array_equal)
 
-from nose.tools import assert_true, assert_false, assert_equal, assert_raises
+from nose.tools import (assert_true, assert_false,
+                        assert_equal, assert_not_equal,
+                        assert_raises)
 
 
 def test_init():
@@ -19,14 +20,14 @@ def test_init():
     assert_equal(fh.filename, 'a_fname')
     assert_true(fh.fileobj is None)
     assert_equal(fh.pos, 0)
-    sio0 = StringIO()
-    fh = FileHolder('a_test', sio0)
+    bio = BytesIO()
+    fh = FileHolder('a_test', bio)
     assert_equal(fh.filename, 'a_test')
-    assert_true(fh.fileobj is sio0)
+    assert_true(fh.fileobj is bio)
     assert_equal(fh.pos, 0)
-    fh = FileHolder('a_test_2', sio0, 3)
+    fh = FileHolder('a_test_2', bio, 3)
     assert_equal(fh.filename, 'a_test_2')
-    assert_true(fh.fileobj is sio0)
+    assert_true(fh.fileobj is bio)
     assert_equal(fh.pos, 3)
 
 
@@ -35,19 +36,53 @@ def test_same_file_as():
     assert_true(fh.same_file_as(fh))
     fh2 = FileHolder('a_test')
     assert_false(fh.same_file_as(fh2))
-    sio0 = StringIO()
-    fh3 = FileHolder('a_fname', sio0)
-    fh4 = FileHolder('a_fname', sio0)
+    bio = BytesIO()
+    fh3 = FileHolder('a_fname', bio)
+    fh4 = FileHolder('a_fname', bio)
     assert_true(fh3.same_file_as(fh4))
     assert_false(fh3.same_file_as(fh))
-    fh5 = FileHolder(fileobj=sio0)
-    fh6 = FileHolder(fileobj=sio0)
+    fh5 = FileHolder(fileobj=bio)
+    fh6 = FileHolder(fileobj=bio)
     assert_true(fh5.same_file_as(fh6))
     # Not if the filename is the same
     assert_false(fh5.same_file_as(fh3))
     # pos doesn't matter
-    fh4_again = FileHolder('a_fname', sio0, pos=4)
+    fh4_again = FileHolder('a_fname', bio, pos=4)
     assert_true(fh3.same_file_as(fh4_again))
 
 
+def test_stamping():
+    # Test stamping works as expected
+    stamper = Stamper()
+    fh1 = FileHolder('a_fname')
+    fh2 = FileHolder('a_fname')
+    assert_equal(stamper(fh1), stamper(fh2))
+    fh3 = FileHolder('a_test')
+    assert_not_equal(stamper(fh1), stamper(fh3))
+    bio = BytesIO()
+    fh4 = FileHolder('a_fname', bio)
+    fh5 = FileHolder('a_fname', bio)
+    assert_equal(stamper(fh4), stamper(fh5))
+    fh6 = FileHolder('a_fname2', bio)
+    assert_not_equal(stamper(fh4), stamper(fh6))
+    assert_equal((fh4.pos, fh5.pos), (0, 0))
+    fh5.pos = 1
+    assert_not_equal(stamper(fh4), stamper(fh5))
+    fh4 = FileHolder(fileobj=bio)
+    fh5 = FileHolder(fileobj=bio)
+    assert_equal(stamper(fh4), stamper(fh5))
+    assert_equal((fh4.pos, fh5.pos), (0, 0))
+    fh5.pos = 1
+    assert_not_equal(stamper(fh4), stamper(fh5))
+
 
+def test_copy_file_map():
+    # Test copy of fileholder using stamping
+    bio = BytesIO()
+    fm = dict(one=FileHolder('a_fname', bio), two=FileHolder('a_fname2'))
+    fm2 = copy_file_map(fm)
+    stamper = Stamper()
+    assert_equal(stamper(fm), stamper(fm2))
+    # Check you can modify the copies independently
+    fm['one'].pos = 2
+    assert_not_equal(stamper(fm), stamper(fm2))
diff --git a/nibabel/tests/test_nifti1.py b/nibabel/tests/test_nifti1.py
index 1f553678c8..0af386aa91 100644
--- a/nibabel/tests/test_nifti1.py
+++ b/nibabel/tests/test_nifti1.py
@@ -20,10 +20,11 @@
 from ..nifti1 import (load, Nifti1Header, Nifti1PairHeader, Nifti1Image,
                       Nifti1Pair, Nifti1Extension, Nifti1Extensions,
                       data_type_codes, extension_codes, slice_order_codes)
+from ..stampers import Stamper
 
 from numpy.testing import assert_array_equal, assert_array_almost_equal
 from nose.tools import (assert_true, assert_false, assert_equal,
-                        assert_raises)
+                        assert_not_equal, assert_raises)
 from nose import SkipTest
 
 from ..testing import data_path
@@ -60,6 +61,21 @@ def test_from_eg_file(self):
         assert_equal(hdr['magic'], asbytes('ni1'))
         assert_equal(hdr['sizeof_hdr'], 348)
 
+    def test_state_stamp(self):
+        super(TestNifti1PairHeader, self).test_state_stamp()
+        # Check that extensions alter state
+        hdr1 = self.header_class(extensions = Nifti1Extensions())
+        hdr2 = self.header_class(extensions = Nifti1Extensions())
+        stamper = Stamper()
+        assert_equal(stamper(hdr1), stamper(hdr2))
+        ext = Nifti1Extension('comment', '123')
+        hdr3 = self.header_class(extensions = Nifti1Extensions((ext,)))
+        assert_not_equal(stamper(hdr1), stamper(hdr3))
+        # No extensions stamp at the moment, so any extensions render the stamps
+        # unequal
+        hdr4 = self.header_class(extensions = Nifti1Extensions((ext,)))
+        assert_not_equal(stamper(hdr3), stamper(hdr4))
+
     def test_nifti_log_checks(self):
         # in addition to analyze header checks
         HC = self.header_class
@@ -172,6 +188,14 @@ def test_binblock_is_file(self):
         hdr.write_to(str_io)
         assert_equal(str_io.getvalue(), hdr.binaryblock + ZEROB * 4)
 
+    def test_state_stamp(self):
+        # Check that this (single) header differs in stamp from pair
+        super(TestNifti1PairHeader, self).test_state_stamp()
+        hdr = self.header_class()
+        super_hdr = Nifti1PairHeader()
+        stamper = Stamper()
+        assert_not_equal(stamper(hdr), stamper(super_hdr))
+
 
 class TestNifti1Image(tana.TestAnalyzeImage):
     # Run analyze-flavor spatialimage tests
@@ -493,6 +517,19 @@ def test_extension_list():
     assert_true(ext_c0 == ext_c1)
 
 
+def test_extension_stamping():
+    # Test we can stamp extension lists
+    ext_c0 = Nifti1Extensions()
+    ext_c1 = Nifti1Extensions()
+    stamper = Stamper()
+    assert_equal(stamper(ext_c0), stamper(ext_c1))
+    ext = Nifti1Extension('comment', '123')
+    ext_c1.append(ext)
+    assert_not_equal(stamper(ext_c0), stamper(ext_c1))
+    ext_c1.remove(ext)
+    assert_equal(stamper(ext_c0), stamper(ext_c1))
+
+
 def test_nifti_extensions():
     nim = load(image_file)
     # basic checks of the available extensions
diff --git a/nibabel/tests/test_spatialimages.py b/nibabel/tests/test_spatialimages.py
index 6ab1a025a4..672e2009b5 100644
--- a/nibabel/tests/test_spatialimages.py
+++ b/nibabel/tests/test_spatialimages.py
@@ -13,6 +13,7 @@
 
 import numpy as np
 
+from ..stampers import Stamper, NdaStamper
 from ..spatialimages import (Header, SpatialImage, HeaderDataError,
                              ImageDataError)
 
@@ -166,6 +167,22 @@ def test_read_data():
     assert_array_equal(data, data2)
 
 
+def test_hdr_state_stamper():
+    # State stamping for template header class
+    stamper = Stamper()
+    hdr1 = Header(np.int32, shape=(1,2,3), zooms=(3.0, 2.0, 1.0))
+    hdr2 = Header(np.int32, shape=(1,2,3), zooms=(3.0, 2.0, 1.0))
+    assert_equal(stamper(hdr1), stamper(hdr2))
+    hdr3 = Header('i4', shape=[1,2,3], zooms=[3.0, 2.0, 1.0])
+    assert_equal(stamper(hdr1), stamper(hdr3))
+    hdr4 = Header('i2', shape=[1,2,3], zooms=[3.0, 2.0, 1.0])
+    assert_not_equal(stamper(hdr1), stamper(hdr4))
+    hdr5 = Header('i4', shape=[6,2,3], zooms=[3.0, 2.0, 1.0])
+    assert_not_equal(stamper(hdr1), stamper(hdr5))
+    hdr6 = Header('i4', shape=[1,2,3], zooms=[3.1, 2.0, 1.0])
+    assert_not_equal(stamper(hdr1), stamper(hdr6))
+
+
 class DataLike(object):
     # Minimal class implementing 'data' API
     shape = (3,)
@@ -258,3 +275,82 @@ def test_get_shape(self):
         assert_equal(img.get_shape(), (1,))
         img = img_klass(np.zeros((2,3,4), np.int16), np.eye(4))
         assert_equal(img.get_shape(), (2,3,4))
+
+    def test_state_stamper(self):
+        img_klass = self.image_class
+        hdr_klass = self.image_class.header_class
+        stamper = NdaStamper()
+        # Assumes all possible images support int16
+        # See https://github.com/nipy/nibabel/issues/58
+        arr = np.arange(5, dtype=np.int16)
+        aff = np.eye(4)
+        img1 = img_klass(arr, aff)
+        img2 = img_klass(arr, aff)
+        # The test depends on the imput array being small enough to stamp
+        assert_equal(img1.current_state(), img2.current_state())
+        assert_equal(img1.current_state(stamper),
+                     img2.current_state(stamper))
+        assert_equal(stamper(img1), stamper(img2))
+        img3 = img_klass(arr + 1, aff)
+        assert_not_equal(img1.current_state(), img3.current_state())
+        assert_not_equal(stamper(img1), stamper(img3))
+        img4 = img_klass(arr, np.diag([1,1,2,1]))
+        assert_not_equal(img1.current_state(), img4.current_state())
+        assert_not_equal(stamper(img1), stamper(img4))
+        # passing a default header should be the same as passing no header
+        hdr = hdr_klass()
+        hdr.set_data_dtype(arr.dtype)
+        img5 = img_klass(arr, aff, hdr)
+        assert_equal(img1.current_state(), img5.current_state())
+        assert_equal(stamper(img1), stamper(img5))
+        # Modifying the filemap makes the images unequal
+        fm_key = list(img_klass.make_file_map().keys())[0]
+        old_filename = img5.file_map[fm_key].filename
+        img5.file_map[fm_key].filename = 'test.img'
+        assert_not_equal(img1.current_state(), img5.current_state())
+        assert_not_equal(stamper(img1), stamper(img5))
+        img5.file_map[fm_key].filename = old_filename
+        assert_equal(img1.current_state(), img5.current_state())
+        assert_equal(stamper(img1), stamper(img5))
+
+    def test_maybe_changed(self):
+        # Mechanism for checking whether image has changed since initialization
+        img_klass = self.image_class
+        arr = np.arange(5, dtype=np.int16)
+        aff = np.eye(4)
+        # All image types need to implement int16
+        img = img_klass(arr, aff)
+        # Get header back that has been customized to this array
+        hdr = img.get_header()
+        # Pass back into image expecting no modifications this time
+        img = img_klass(arr, aff, hdr)
+        assert_false(img.maybe_changed())
+        # Changes to affine or header used in init do not change img
+        aff[0,0] = 1.1
+        assert_false(img.maybe_changed())
+        hdr.set_zooms((2,))
+        assert_false(img.maybe_changed())
+        # Changing the affine, header in the image does cause change
+        iaff = img.get_affine()
+        ihdr = img.get_header()
+        iaff[0,0] = 1.2
+        assert_true(img.maybe_changed())
+        # we can reset
+        img.reset_changed()
+        assert_false(img.maybe_changed())
+        ihdr.set_zooms((3,))
+        assert_true(img.maybe_changed())
+        # we can reset
+        img.reset_changed()
+        assert_false(img.maybe_changed())
+        # Data changes always result in image changes
+        arr[0] = 99
+        assert_true(img.maybe_changed())
+        img.reset_changed()
+        # Filemap changes change too
+        fm_key = list(img_klass.make_file_map().keys())[0]
+        old_filename = img.file_map[fm_key].filename
+        img.file_map[fm_key].filename = 'test.img'
+        assert_true(img.maybe_changed())
+        img.file_map[fm_key].filename = old_filename
+        assert_false(img.maybe_changed())
diff --git a/nibabel/tests/test_stampers.py b/nibabel/tests/test_stampers.py
new file mode 100644
index 0000000000..5b8c709afe
--- /dev/null
+++ b/nibabel/tests/test_stampers.py
@@ -0,0 +1,122 @@
+""" Testing state stamping
+"""
+
+import numpy as np
+
+from ..py3k import ZEROB, asbytes
+
+from ..stampers import Unknown, is_unknown, Stamper, NdaStamper
+
+from numpy.testing import (assert_array_almost_equal,
+                           assert_array_equal)
+
+from nose.tools import (assert_true, assert_false, assert_equal,
+                        assert_not_equal, assert_raises)
+
+
+def test_uknown():
+    # Unknown singleton-like
+    u = Unknown()
+    assert_equal(repr(u), 'Unknown()')
+    assert_equal(str(u), 'Unknown()')
+    assert_false(u == u)
+    assert_true(u != u)
+    assert_not_equal(u, u)
+    p = Unknown()
+    assert_not_equal(u, p)
+    assert_true(is_unknown(u))
+    assert_false(is_unknown(1))
+    # Note - this _is_ equal
+    # assert_not_equal((1, u), (1, u))
+
+
+def test_stamper():
+    # state_stamp can can from
+    # * state_stamper() method
+    # Some immutables -> themselves
+    # Otherwise the signature is Unknown()
+    class D(object): # Class for testing get_signature
+        def state_stamper(self, cstate):
+            return self.__class__, 28
+    for ster in (Stamper(), NdaStamper()):
+        assert_equal(ster(None), ster(None))
+        assert_not_equal(ster(None), ster(1))
+        assert_equal(ster(1), ster(1))
+        assert_not_equal(ster(1), ster(2))
+        assert_equal(ster('a string'), ster('a string'))
+        assert_not_equal(ster('a string'), ster(1))
+        bs = asbytes('some bytes')
+        assert_equal(ster(bs), ster(bs))
+        assert_equal(ster(1.0), ster(1.0))
+        assert_not_equal(ster(1.0), ster(1))
+        # an anonymous object, usually not stampable
+        ob = object()
+        assert_not_equal(ster(ob), ster(ob))
+        L = [1, 2]
+        T = (1, 2)
+        assert_equal(ster(L), ster(L[:]))
+        assert_equal(ster(T), ster(T[:]))
+        assert_not_equal(ster(L), ster(T))
+        assert_not_equal(ster((1, ob)), ster((1, ob)))
+        d1 = D()
+        d2 = D()
+        assert_equal(ster(d1), ster(d2))
+        # Dictionaries
+        di1 = dict(a = 1, b = 2)
+        # Entry order does not matter - but this is difficult to test because
+        # key order is not defined
+        di2 = dict(b = 2, a = 1)
+        assert_equal(ster(di1), ster(di2))
+        # Dictionaries different
+        assert_not_equal(ster(di1), ster(dict(b = 2, a = 2)))
+        assert_not_equal(ster(di1), ster(dict(b = 2, c = 1)))
+        # They are not just defined by their items, but by their type
+        assert_not_equal(ster(di1), ster(di2.items()))
+        # Inherited types don't work because they might have more state
+        class MyList(list): pass
+        class MyTuple(tuple): pass
+        class MyDict(dict): pass
+        assert_not_equal(ster(MyList((1,2))), ster(MyList((1,2))))
+        assert_not_equal(ster(MyTuple((1,2))), ster(MyTuple((1,2))))
+        assert_not_equal(ster(MyDict(a=1, b=2)), ster(MyDict(a=1, b=2)))
+        # Classes pass through, even if they have state_stamper methods
+        assert_equal(ster(D), ster(D))
+
+
+def test_nda_stamper():
+    # Arrays work if they are small
+    nda_ster = NdaStamper()
+    arr1 = np.zeros((3,), dtype=np.int16)
+    arr2 = np.zeros((3,), dtype=np.int16)
+    assert_equal(nda_ster(arr1), nda_ster(arr2))
+    # The data has to be the same
+    arr2p1 = arr2.copy()
+    arr2p1[0] = 1
+    assert_not_equal(nda_ster(arr1), nda_ster(arr2p1))
+    # Comparison depends on the byte threshold
+    nda_ster5 = NdaStamper(byte_thresh = 5)
+    assert_not_equal(nda_ster5(arr1), nda_ster5(arr1))
+    # Byte thresh gets passed down to iterations of lists
+    assert_equal(nda_ster([1, arr1]), nda_ster([1, arr2]))
+    assert_not_equal(nda_ster5([1, arr1]), nda_ster5([1, arr1]))
+    # Arrays in dicts
+    d1 = dict(a = 1, b = arr1)
+    d2 = dict(a = 1, b = arr2)
+    assert_equal(nda_ster(d1), nda_ster(d2))
+    # Byte thresh gets passed down to iterations of dicts
+    assert_not_equal(nda_ster5(d1), nda_ster5(d1))
+    # Make sure strings distinguished from arrays
+    bs = asbytes('byte string')
+    sarr = np.array(bs, dtype = 'S')
+    assert_equal(nda_ster(sarr), nda_ster(sarr.copy()))
+    assert_not_equal(nda_ster(sarr), nda_ster(bs))
+    # shape and dtype also distinguished
+    arr3 = arr2.reshape((1,3))
+    assert_not_equal(nda_ster(arr1), nda_ster(arr3))
+    arr4 = arr3.reshape((3,))
+    assert_equal(nda_ster(arr1), nda_ster(arr4))
+    arr5 = arr1.newbyteorder('s')
+    assert_array_equal(arr1, arr5)
+    assert_not_equal(nda_ster(arr1), nda_ster(arr5))
+    arr6 = arr5.newbyteorder('s')
+    assert_equal(nda_ster(arr1), nda_ster(arr6))