From fc55842d9d6814795a13793dd1d430b8447b2c62 Mon Sep 17 00:00:00 2001
From: Jan Caron <j.caron@fz-juelich.de>
Date: Tue, 3 Feb 2015 23:35:26 +0100
Subject: [PATCH] Mainly implementation of diagnostics class! ... And stuff!
 docstrings: updated! costfunction: CFAdapterScipyCG obsolete, deleted
 estimate_lambda() dataset: use_fftw and threads obsolete diagnostics:
 implemented stuff... much stuff... fft: Docstrings and implementation of
 threads magcreator: fixed bug in slab (dimension mix-up) phasemap: plot now
 also returns colorbar reconstruction: PrintIterator and
 optimize_simple_leastsq obsolete regularisator: Docstrings scripts: minor
 modifications scripts - reconstruct_random_pixels: now use optimize_linear
 testcases: analytic, compliance, kernel, magcreator, magdata, projector

---
 pyramid/__init__.py                           |   26 +-
 pyramid/analytic.py                           |    3 +
 pyramid/costfunction.py                       |   71 +-
 pyramid/dataset.py                            |   39 +-
 pyramid/diagnostics.py                        |  241 +++-
 pyramid/fft.py                                |  126 +-
 pyramid/forwardmodel.py                       |    4 +-
 pyramid/kernel.py                             |   44 +-
 pyramid/magcreator.py                         |   14 +-
 pyramid/magdata.py                            | 1278 ++++++++---------
 pyramid/numcore/phasemapper_core.pyx          |    2 -
 pyramid/phasemap.py                           |   17 +-
 pyramid/phasemapper.py                        |    7 +-
 pyramid/projector.py                          |   11 +-
 pyramid/reconstruction.py                     |  150 +-
 pyramid/regularisator.py                      |  166 ++-
 pyramid/version.py                            |    2 +-
 .../patrick_nanowire_reconstruction.py        |   25 +-
 ...rick_nanowire_reconstruction_simulation.py |    2 +-
 scripts/collaborations/rueffner_file.py       |    8 +-
 scripts/collaborations/vtk_conversion.py      |  195 +++
 .../vtk_conversion_nanowire_full_90deg.py     |    6 +-
 .../reconstruct_random_pixels.py              |    7 +-
 .../reconstruction_linear_test.py             |   52 +-
 .../reconstruction_linear_test_batch.py       |    2 +-
 ...econstruction_sparse_cg_singular_values.py |    2 +-
 scripts/test methods/histo_norm.py            |   39 +
 tests/test_analytic.py                        |    2 +-
 tests/test_compliance.py                      |    4 +-
 tests/test_kernel.py                          |   41 +-
 tests/test_kernel/ref_u.npy                   |  Bin 0 -> 276 bytes
 tests/test_kernel/ref_u_fft.npy               |  Bin 0 -> 400 bytes
 tests/test_kernel/ref_v.npy                   |  Bin 0 -> 276 bytes
 tests/test_kernel/ref_v_fft.npy               |  Bin 0 -> 400 bytes
 tests/test_magcreator.py                      |   16 +
 tests/test_magcreator/ref_ellipse_x.npy       |  Bin 0 -> 584 bytes
 tests/test_magcreator/ref_ellipse_y.npy       |  Bin 0 -> 584 bytes
 tests/test_magcreator/ref_ellipse_z.npy       |  Bin 0 -> 584 bytes
 tests/test_magcreator/ref_ellipsoid.npy       |  Bin 0 -> 584 bytes
 tests/test_magdata.py                         |  103 +-
 tests/test_magdata/ref_mag_data.nc            |  Bin 8474 -> 7684 bytes
 tests/test_projector.py                       |   68 +-
 tests/test_projector/ref_mag_data.nc          |  Bin 0 -> 8356 bytes
 tests/test_projector/ref_mag_data.txt         |  122 --
 tests/test_projector/ref_mag_proj_x.nc        |  Bin 0 -> 7276 bytes
 tests/test_projector/ref_mag_proj_x00.nc      |  Bin 0 -> 7204 bytes
 tests/test_projector/ref_mag_proj_x45.nc      |  Bin 0 -> 7204 bytes
 tests/test_projector/ref_mag_proj_x90.nc      |  Bin 0 -> 7204 bytes
 tests/test_projector/ref_mag_proj_y.nc        |  Bin 0 -> 7204 bytes
 tests/test_projector/ref_mag_proj_y00.nc      |  Bin 0 -> 7276 bytes
 tests/test_projector/ref_mag_proj_y45.nc      |  Bin 0 -> 7276 bytes
 tests/test_projector/ref_mag_proj_y90.nc      |  Bin 0 -> 7276 bytes
 tests/test_projector/ref_mag_proj_z.nc        |  Bin 0 -> 7156 bytes
 tests/test_projector/ref_proj_x.txt           |    4 -
 tests/test_projector/ref_proj_y.txt           |    4 -
 tests/test_projector/ref_proj_z.txt           |    5 -
 56 files changed, 1665 insertions(+), 1243 deletions(-)
 create mode 100644 scripts/collaborations/vtk_conversion.py
 create mode 100644 scripts/test methods/histo_norm.py
 create mode 100644 tests/test_kernel/ref_u.npy
 create mode 100644 tests/test_kernel/ref_u_fft.npy
 create mode 100644 tests/test_kernel/ref_v.npy
 create mode 100644 tests/test_kernel/ref_v_fft.npy
 create mode 100644 tests/test_magcreator/ref_ellipse_x.npy
 create mode 100644 tests/test_magcreator/ref_ellipse_y.npy
 create mode 100644 tests/test_magcreator/ref_ellipse_z.npy
 create mode 100644 tests/test_magcreator/ref_ellipsoid.npy
 create mode 100644 tests/test_projector/ref_mag_data.nc
 delete mode 100644 tests/test_projector/ref_mag_data.txt
 create mode 100644 tests/test_projector/ref_mag_proj_x.nc
 create mode 100644 tests/test_projector/ref_mag_proj_x00.nc
 create mode 100644 tests/test_projector/ref_mag_proj_x45.nc
 create mode 100644 tests/test_projector/ref_mag_proj_x90.nc
 create mode 100644 tests/test_projector/ref_mag_proj_y.nc
 create mode 100644 tests/test_projector/ref_mag_proj_y00.nc
 create mode 100644 tests/test_projector/ref_mag_proj_y45.nc
 create mode 100644 tests/test_projector/ref_mag_proj_y90.nc
 create mode 100644 tests/test_projector/ref_mag_proj_z.nc
 delete mode 100644 tests/test_projector/ref_proj_x.txt
 delete mode 100644 tests/test_projector/ref_proj_y.txt
 delete mode 100644 tests/test_projector/ref_proj_z.txt

diff --git a/pyramid/__init__.py b/pyramid/__init__.py
index ad7b174..47aba58 100644
--- a/pyramid/__init__.py
+++ b/pyramid/__init__.py
@@ -30,6 +30,8 @@ reconstruction
     Reconstruct magnetic distributions from given phasemaps.
 regularisator
     Class to instantiate different regularisation strategies.
+diagnostics
+    Class to calculate diagnostics
 fft
     Class for custom FFT functions using numpy or FFTW.
 
@@ -41,31 +43,33 @@ numcore
 """
 
 
-import logging
-
 from . import analytic
 from . import magcreator
 from . import reconstruction
 from . import fft
-from .costfunction import *
-from .dataset import *
-from .forwardmodel import *
-from .kernel import *
-from .magdata import *
-from .phasemap import *
-from .phasemapper import *
-from .projector import *
-from .regularisator import *
+from .costfunction import *  # analysis:ignore
+from .dataset import *  # analysis:ignore
+from .diagnostics import *  # analysis:ignore
+from .forwardmodel import *  # analysis:ignore
+from .kernel import *  # analysis:ignore
+from .magdata import *  # analysis:ignore
+from .phasemap import *  # analysis:ignore
+from .phasemapper import *  # analysis:ignore
+from .projector import *  # analysis:ignore
+from .regularisator import *  # analysis:ignore
 from .version import version as __version__
 from .version import hg_revision as __hg_revision__
 
+import logging
 _log = logging.getLogger(__name__)
 _log.info("Starting PYRAMID V{} HG{}".format(__version__, __hg_revision__))
 del logging
 
+
 __all__ = ['__version__', '__hg_revision__', 'analytic', 'magcreator', 'reconstruction', 'fft']
 __all__.extend(costfunction.__all__)
 __all__.extend(dataset.__all__)
+__all__.extend(diagnostics.__all__)
 __all__.extend(forwardmodel.__all__)
 __all__.extend(kernel.__all__)
 __all__.extend(magdata.__all__)
diff --git a/pyramid/analytic.py b/pyramid/analytic.py
index 5faef31..3585c89 100644
--- a/pyramid/analytic.py
+++ b/pyramid/analytic.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """Create phase maps for magnetic distributions with analytic solutions.
 
 This module provides methods for the calculation of the magnetic phase for simple geometries for
diff --git a/pyramid/costfunction.py b/pyramid/costfunction.py
index 50c41ff..fb67e45 100644
--- a/pyramid/costfunction.py
+++ b/pyramid/costfunction.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """This module provides the :class:`~.Costfunction` class which represents a strategy to calculate
 the so called `cost` of a threedimensional magnetization distribution."""
 
@@ -6,7 +9,6 @@ the so called `cost` of a threedimensional magnetization distribution."""
 import numpy as np
 
 from scipy.sparse import eye as sparse_eye
-from scipy.sparse.linalg import LinearOperator
 
 from pyramid.forwardmodel import ForwardModel
 from pyramid.regularisator import NoneRegularisator
@@ -34,7 +36,6 @@ class Costfunction(object):
         :class:`~dataset.DataSet` object, which stores all information for the cost calculation.
     regularisator : :class:`~.Regularisator`
         Regularisator class that's responsible for the regularisation term.
-    regularisator: :class:`~Regularisator`
     y : :class:`~numpy.ndarray` (N=1)
         Vector which lists all pixel values of all phase maps one after another.
     fwd_model : :class:`~.ForwardModel`
@@ -131,70 +132,12 @@ class Costfunction(object):
                 + self.regularisator.hess_dot(x, vector))
 
     def hess_diag(self, _):
-        # TODO: Docstring!
-        # TODO: What for again?
-        return np.ones(self.n)
-
-    def estimate_lambda(self):
-        # TODO: Docstring!
-        # TODO: Not very efficient? Is this even correct?
-
-        def unit_vec(length, index):
-            result = np.zeros(length)
-            result[index] = 1
-            return result
-
-        fwd, reg = self.fwd_model, self.regularisator
-        trace_fwd = np.sum([fwd.jac_T_dot(None, fwd.jac_dot(None, unit_vec(self.n, i)))
-                            for i in range(self.n)])
-        trace_reg = np.sum([reg(unit_vec(self.n, i)) for i in range(self.n)])
-        print 'fwd:', trace_fwd
-        print 'reg:', trace_reg
-        import pdb; pdb.set_trace()
-        return trace_fwd / trace_reg
-
-
-class CFAdapterScipyCG(LinearOperator):
-
-    '''Adapter class making the :class:`~.Costfunction` class accessible for scipy cg methods.
-
-    This class provides an adapter for the :class:`~.Costfunction` to be usable with the
-    :func:`~.scipy.sparse.linalg.cg` function. the :func:`~.matvec` function is overwritten to
-    implement a multiplication with the Hessian of the adapted costfunction. This is used in the
-    :func:`~pyramid.reconstruction.optimise_sparse_cg` function of the
-    :mod:`~pyramid.reconstruction` module.
-
-    Attributes
-    ----------
-    cost : :class:`~.Costfunction`
-        Costfunction which should be made usable in the :func:`~.scipy.sparse.linalg.cg` function.
-
-    '''
-    # TODO: make obsolete!
-
-    _log = logging.getLogger(__name__+'.CFAdapterScipyCG')
-
-    def __init__(self, cost):
-        self._log.debug('Calling __init__')
-        self.cost = cost
-
-    def matvec(self, vector):
-        '''Matrix-vector multiplication with the Hessian of the adapted costfunction.
+        ''' Return the diagonal of the Hessian.
 
         Parameters
         ----------
-        vector : :class:`~numpy.ndarray` (N=1)
-            Vector which will be multiplied by the Hessian matrix provided by the adapted
-            costfunction.
+        _ : undefined
+            Unused input
 
         '''
-        self._log.debug('Calling matvec')
-        return self.cost.hess_dot(None, vector)
-
-    @property
-    def shape(self):
-        return (self.cost.data_set.n, self.cost.data_set.n)
-
-    @property
-    def dtype(self):
-        return np.dtype("d")
+        return np.ones(self.n)
diff --git a/pyramid/dataset.py b/pyramid/dataset.py
index 9f422ca..a1924fb 100644
--- a/pyramid/dataset.py
+++ b/pyramid/dataset.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """This module provides the :class:`~.DataSet` class for the collection of phase maps
 and additional data like corresponding projectors."""
 
@@ -49,6 +52,9 @@ class DataSet(object):
         A list of all stored :class:`~.PhaseMap` objects.
     phase_vec: :class:`~numpy.ndarray` (N=1)
         The concatenaded, vectorized phase of all ;class:`~.PhaseMap` objects.
+    Se_inv : :class:`~numpy.ndarray` (N=2), optional
+        Inverted covariance matrix of the measurement errors. The matrix has size `NxN` with N
+        being the length of the targetvector y (vectorized phase map information).
     m: int
         Size of the image space.
     n: int
@@ -76,11 +82,10 @@ class DataSet(object):
     @property
     def phase_mappers(self):
         dim_uv_set = set([p.dim_uv for p in self.projectors])
-        kernel_list = [Kernel(self.a, dim_uv, threads=self.threads) for dim_uv in dim_uv_set]
+        kernel_list = [Kernel(self.a, dim_uv) for dim_uv in dim_uv_set]
         return {kernel.dim_uv: PhaseMapperRDFC(kernel) for kernel in kernel_list}
 
-    def __init__(self, a, dim, b_0=1, mask=None, Se_inv=None, use_fftw=True, threads=1):
-        # TODO: document!
+    def __init__(self, a, dim, b_0=1, mask=None, Se_inv=None):
         self._log.debug('Calling __init__')
         assert isinstance(a, Number), 'Grid spacing has to be a number!'
         assert a >= 0, 'Grid spacing has to be a positive number!'
@@ -96,8 +101,6 @@ class DataSet(object):
         self.b_0 = b_0
         self.mask = mask
         self.Se_inv = Se_inv
-        self.use_fftw = use_fftw
-        self.threads = threads
         self.phase_maps = []
         self.projectors = []
         self._log.debug('Created: '+str(self))
@@ -152,11 +155,33 @@ class DataSet(object):
                 for projector in self.projectors]
 
     def set_Se_inv_block_diag(self, cov_list):
-        # TODO: Document!
+        '''Set the Se_inv matrix as a block diagonal matrix
+
+        Parameters
+        ----------
+        cov_list: list of :class:`~numpy.ndarray`
+            List of inverted covariance matrices (one for each projection).
+
+        Returns
+        -------
+            None
+
+        '''
         self.Se_inv = sparse.block_diag(cov_list).tocsr()
 
     def set_Se_inv_diag_with_masks(self, mask_list):
-        # TODO: Document!
+        '''Set the Se_inv matrix as a block diagonal matrix from a list of masks
+
+        Parameters
+        ----------
+        mask_list: list of :class:`~numpy.ndarray`
+            List of 2D masks (one for each projection) which define trust regions.
+
+        Returns
+        -------
+            None
+
+        '''
         cov_list = [sparse.diags(m.flatten(), 0) for m in mask_list]
         self.set_Se_inv_block_diag(cov_list)
 
diff --git a/pyramid/diagnostics.py b/pyramid/diagnostics.py
index fbec1ff..b9d84bd 100644
--- a/pyramid/diagnostics.py
+++ b/pyramid/diagnostics.py
@@ -1,12 +1,13 @@
 # -*- coding: utf-8 -*-
-"""
-Created on Thu Dec 11 17:20:27 2014
-
-@author: Jan
-"""
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
+"""This module provides the :class:`~.Diagnostics` class for the calculation of diagnostics of a
+specified costfunction for a fixed magnetization distribution."""
 
 
 import numpy as np
+import matplotlib.pyplot as plt
 
 import jutil
 
@@ -14,50 +15,80 @@ from pyramid import fft
 from pyramid.magdata import MagData
 from pyramid.phasemap import PhaseMap
 
+
+__all__ = ['Diagnostics']
+
+
 class Diagnostics(object):
 
-    # TODO: Docstrings and position of properties!
+    '''Class for calculating diagnostic properties of a specified costfunction.
 
-    def __init__(self, x_rec, cost, max_iter=100):
-        self.x_rec = x_rec
-        self.cost = cost
-        self.max_iter = max_iter
-        self.fwd_model = cost.fwd_model
-        self.Se_inv = self.cost.Se_inv
-        self.dim = self.cost.data_set.dim
-        self.row_idx = None
-        self.set_position(0)#(0, self.dim[0]//2, self.dim[1]//2, self.dim[2]//2))
-        self._A = jutil.operator.CostFunctionOperator(self.cost, self.x_rec)
-        self._P = jutil.preconditioner.CostFunctionPreconditioner(self.cost, self.x_rec)
+    For the calculation of diagnostic properties, a costfunction and a magnetization distribution
+    are specified at construction. With the :func:`~.set_position`, a position in 3D space can be
+    set at which all properties will be calculated. Properties are saved via boolean flags and
+    thus, calculation is only done if the position has changed in between. The standard deviation
+    and the measurement contribution require the execution of a conjugate gradient solver and can
+    take a while for larger problems.
 
-    def set_position(self, pos):
-        # TODO: Does not know about the mask... thus gives wrong results or errors
-#        m, z, y, x = pos
-#        row_idx = m*np.prod(self.dim) + z*self.dim[1]*self.dim[2] + y*self.dim[2] + x
-        row_idx = pos
-        if row_idx != self.row_idx:
-            self.row_idx = row_idx
-            self._updated_std = False
-            self._updated_gain_row = False
-            self._updated_avrg_kern_row = False
-            self._updated_measure_contribution = False
+    Attributes
+    ----------
+    x_rec: :class:`~numpy.ndarray`
+        Vectorized magnetization distribution at which the costfunction is evaluated.
+    cost: :class:`~.pyramid.costfunction.Costfunction`
+        Costfunction for which the diagnostics are calculated.
+    max_iter: int, optional
+        Maximum number of iterations. Default is 1000.
+    fwd_model: :class:`~pyramid.forwardmodel.ForwardModel`
+        Forward model used in the costfunction.
+    Se_inv : :class:`~numpy.ndarray` (N=2), optional
+        Inverted covariance matrix of the measurement errors. The matrix has size `NxN` with N
+        being the length of the targetvector y (vectorized phase map information).
+    dim: tuple (N=3)
+        Dimensions of the 3D magnetic distribution.
+    row_idx: int
+        Row index of the system matrix corresponding to the current position in 3D space.
+    cov_row: :class:`~numpy.ndarray`
+        Row of the covariance matrix (``S_a^-1+F'(x_f)^T S_e^-1 F'(x_f)``) which is needed for the
+        calculation of the gain and averaging kernel matrizes and which ideally contains the
+        variance at position `row_idx` for the current component and position in 3D.
+    std: float
+        Standard deviation of the chosen component at the current position (calculated when
+        needed).
+    gain_row: :class:`~numpy.ndarray`
+        Row of the gain matrix, which maps differences of phase measurements onto differences in
+        the retrieval result of the magnetization distribution(calculated when needed).
+    avrg_kern_row: :class:`~numpy.ndarray`
+        Row of the averaging kernel matrix (which is ideally the identity matrix), which describes
+        the smoothing introduced by the regularization (calculated when needed).
+    measure_contribution: :class:`~numpy.ndarray`
+
+    Notes
+    -----
+        Some properties depend on others, which may require recalculations of these prior
+        properties if necessary. The dependencies are ('-->' == 'requires'):
+        avrg_kern_row --> gain_row --> std --> m_inv_row
+        measure_contribution is independant
+
+    '''
 
     @property
-    def std(self):
-        if not self._updated_std:
+    def cov_row(self):
+        if not self._updated_cov_row:
             e_i = fft.zeros(self.cost.n, dtype=fft.FLOAT)
             e_i[self.row_idx] = 1
-            row = jutil.cg.conj_grad_solve(self._A, e_i, P=self._P, max_iter=self.max_iter)
-            self._m_inv_row = row
-            self._std = np.sqrt(self._m_inv_row[self.row_idx])
-            self._updated_std = True
-        return self._std
+            row = 2 * jutil.cg.conj_grad_solve(self._A, e_i, P=self._P, max_iter=self.max_iter)
+            self._std_row = row
+            self._updated_cov_row = True
+        return self._std_row
+
+    @property
+    def std(self):
+        return np.sqrt(self.cov_row[self.row_idx])
 
     @property
     def gain_row(self):
         if not self._updated_gain_row:
-            self.std  # evoke to update self._m_inv_row if necessary # TODO: make _m_inv_row checked!
-            self._gain_row = self.Se_inv.dot(self.fwd_model.jac_dot(self.x_rec, self._m_inv_row))
+            self._gain_row = self.Se_inv.dot(self.fwd_model.jac_dot(self.x_rec, self.cov_row))
             self._updated_gain_row = True
         return self._gain_row
 
@@ -73,24 +104,142 @@ class Diagnostics(object):
         if not self._updated_measure_contribution:
             cache = self.fwd_model.jac_dot(self.x_rec, fft.ones(self.cost.n, fft.FLOAT))
             cache = self.fwd_model.jac_T_dot(self.x_rec, self.Se_inv.dot(cache))
-            mc = jutil.cg.conj_grad_solve(self._A, cache, P=self._P, max_iter=self.max_iter)
+            mc = 2 * jutil.cg.conj_grad_solve(self._A, cache, P=self._P, max_iter=self.max_iter)
             self._measure_contribution = mc
             self._updated_measure_contribution = True
         return self._measure_contribution
 
-    def get_avg_kernel(self, pos=None):
+    @property
+    def pos(self):
+        return self._pos
+
+    @pos.setter
+    def pos(self, pos):
+        c, z, y, x = pos
+        assert self.mask[z, y, x], 'Position is outside of the provided mask!'
+        mask_vec = self.mask.flatten()
+        idx_3d = z*self.dim[1]*self.dim[2] + y*self.dim[2] + x
+        row_idx = c*np.prod(mask_vec.sum()) + mask_vec[:idx_3d].sum()
+        if row_idx != self.row_idx:
+            self._pos = pos
+            self.row_idx = row_idx
+            self._updated_cov_row = False
+            self._updated_gain_row = False
+            self._updated_avrg_kern_row = False
+            self._updated_measure_contribution = False
+
+    def __init__(self, x_rec, cost, max_iter=1000):
+        self.x_rec = x_rec
+        self.cost = cost
+        self.max_iter = max_iter
+        self.fwd_model = cost.fwd_model
+        self.Se_inv = cost.Se_inv
+        self.dim = cost.data_set.dim
+        self.mask = cost.data_set.mask
+        self.row_idx = None
+        self.pos = (0,) + tuple(np.array(np.where(self.mask))[:, 0])  # first True mask entry
+        self._updated_cov_row = False
+        self._updated_gain_row = False
+        self._updated_avrg_kern_row = False
+        self._updated_measure_contribution = False
+        self._A = jutil.operator.CostFunctionOperator(self.cost, self.x_rec)
+        self._P = jutil.preconditioner.CostFunctionPreconditioner(self.cost, self.x_rec)
+
+    def get_avg_kern_row(self, pos=None):
+        '''Get the averaging kernel matrix row represented as a 3D magnetization distribution.
+
+        Parameters
+        ----------
+        pos: tuple (N=4)
+            Position in 3D plus component `(c, z, y, x)`
+
+        Returns
+        -------
+        mag_data_avg_kern: :class:`~pyramid.magdata.MagData`
+            Averaging kernel matrix row represented as a 3D magnetization distribution
+
+        '''
         if pos is not None:
-            self.set_position(pos)
+            self.pos = pos
         mag_data_avg_kern = MagData(self.cost.data_set.a, fft.zeros((3,)+self.dim))
-        mag_data_avg_kern.set_vector(self.avrg_kern_row, mask=self.cost.data_set.mask)
+        mag_data_avg_kern.set_vector(self.avrg_kern_row, mask=self.mask)
         return mag_data_avg_kern
 
-    def get_gain_maps(self, pos=None):
+    def calculate_averaging(self, pos=None):
+        '''Get the gain matrix row represented as a list of 2D phase maps.
+
+        Parameters
+        ----------
+        pos: tuple (N=4)
+            Position in 3D plus component `(c, z, y, x)`
+
+        Returns
+        -------
+        gain_map_list: list of :class:`~pyramid.phasemap.PhaseMap`
+            Gain matrix row represented as a list of 2D phase maps.
+
+        '''# TODO: Docstring!
+        mag_data_avg_kern = self.get_avg_kern_row(pos)
+        print self.pos
+        mag_x, mag_y, mag_z = mag_data_avg_kern.magnitude
+        x = mag_x.sum(axis=(0, 1))
+        y = mag_y.sum(axis=(0, 2))
+        z = mag_z.sum(axis=(1, 2))
+        plt.figure()
+        plt.axhline(y=0, ls='-', color='k')
+        plt.axhline(y=1, ls='-', color='k')
+        plt.plot(x, label='x', color='r', marker='o')
+        plt.plot(y, label='y', color='g', marker='o')
+        plt.plot(z, label='z', color='b', marker='o')
+        c = self.pos[0]
+        data = [x, y, z][c]
+        col = ['r', 'g', 'b'][c]
+        i_m = np.argmax(data)  # Index of the maximum
+        plt.axhline(y=data[i_m], ls='-', color=col)
+        plt.axhline(y=data[i_m]/2, ls='--', color=col)
+        # Left side:
+        for i in np.arange(i_m-1, -1, -1):
+            if data[i] < data[i_m]/2:
+                l = (data[i_m]/2-data[i])/(data[i+1]-data[i]) + i
+                break
+        # Right side:
+        for i in np.arange(i_m+1, data.size):
+            if data[i] < data[i_m]/2:
+                r = (data[i_m]/2-data[i-1])/(data[i]-data[i-1]) + i-1
+                break
+        # Calculate FWHM:
+        fwhm = r - l
+        px_avrg = 1 / fwhm
+        plt.vlines(x=[l, r], ymin=0, ymax=data[i_m]/2, linestyles=':', color=col)
+        # Add legend:
+        plt.legend()
+        return px_avrg, fwhm, (x, y, z)
+
+    def get_gain_row_maps(self, pos=None):
+        '''Get the gain matrix row represented as a list of 2D (inverse) phase maps.
+
+        Parameters
+        ----------
+        pos: tuple (N=4)
+            Position in 3D plus component `(c, z, y, x)`
+
+        Returns
+        -------
+        gain_map_list: list of :class:`~pyramid.phasemap.PhaseMap`
+            Gain matrix row represented as a list of 2D phase maps
+
+        Notes
+        -----
+        Note that the produced gain maps define the magnetization change at the current position
+        in 3d per phase change at the position of the . Take this into account when plotting the
+        maps (1/rad instead of rad).
+
+        '''
         if pos is not None:
-            self.set_position(pos)
+            self.pos = pos
         hp = self.cost.data_set.hook_points
-        result = []
+        gain_map_list = []
         for i, projector in enumerate(self.cost.data_set.projectors):
             gain = self.gain_row[hp[i]:hp[i+1]].reshape(projector.dim_uv)
-            result.append(PhaseMap(self.cost.data_set.a, gain))
-        return result
+            gain_map_list.append(PhaseMap(self.cost.data_set.a, gain))
+        return gain_map_list
diff --git a/pyramid/fft.py b/pyramid/fft.py
index 1c7733d..5baea7f 100644
--- a/pyramid/fft.py
+++ b/pyramid/fft.py
@@ -1,11 +1,16 @@
 # -*- coding: utf-8 -*-
-"""
-Created on Fri Nov 28 15:30:10 2014
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
+"""Custom FFT module with numpy and FFTW support.
+
+This module provides custom methods for FFTs including inverse, adjoint and real variants. The
+FFTW library is supported and is used as a default if the import succeeds. Otherwise the numpy.fft
+pack will be used. FFTW objects are saved in a cache after creation which speeds up further similar
+FFT operations.
 
-@author: Jan
 """
 
-# TODO: Document!
 
 import numpy as np
 import cPickle as pickle
@@ -27,9 +32,20 @@ except ImportError:
     BACKEND = 'numpy'
     print("pyFFTW module not found. Using numpy implementation.")
 
+try:
+    import psutil
+    NTHREADS = psutil.cpu_count()
+except ImportError:
+    try:
+        import multiprocessing
+        NTHREADS = multiprocessing.cpu_count()
+    except ImportError:
+        NTHREADS = 1
+
 
-__all__ = ['PLANS', 'FLOAT', 'COMPLEX', 'NTHREADS', 'dump_wisdom', 'load_wisdom', 'zeros', 'empty',
-           'configure_backend', 'fftn', 'ifftn', 'rfftn', 'irfftn', 'rfftn_adj', 'irfftn_adj']
+__all__ = ['PLANS', 'FLOAT', 'COMPLEX', 'NTHREADS', 'dump_wisdom', 'load_wisdom', #analysis:ignore
+           'zeros', 'empty', 'configure_backend',
+           'fftn', 'ifftn', 'rfftn', 'irfftn', 'rfftn_adj', 'irfftn_adj']
 
 
 class FFTWCache(object):
@@ -53,7 +69,6 @@ class FFTWCache(object):
 PLANS = FFTWCache()
 FLOAT = np.float32      # One convenient place to
 COMPLEX = np.complex64  # change from 32 to 64 bit
-NTHREADS = 1
 
 
 # Numpy functions:
@@ -98,7 +113,7 @@ def _fftn_fftw(a, s=None, axes=None):
         raise TypeError('Wrong input type!')
     fftw = PLANS.lookup_fftw('fftn', a, s, axes, NTHREADS)
     if fftw is None:
-        fftw = pyfftw.builders.fftn(a, s, axes)
+        fftw = pyfftw.builders.fftn(a, s, axes, threads=NTHREADS)
         PLANS.add_fftw('fftn', fftw, s, axes, NTHREADS)
     return fftw(a).copy()
 
@@ -108,7 +123,7 @@ def _ifftn_fftw(a, s=None, axes=None):
         raise TypeError('Wrong input type!')
     fftw = PLANS.lookup_fftw('ifftn', a, s, axes, NTHREADS)
     if fftw is None:
-        fftw = pyfftw.builders.ifftn(a, s, axes)
+        fftw = pyfftw.builders.ifftn(a, s, axes, threads=NTHREADS)
         PLANS.add_fftw('ifftn', fftw, s, axes, NTHREADS)
     return fftw(a).copy()
 
@@ -118,7 +133,7 @@ def _rfftn_fftw(a, s=None, axes=None):
         raise TypeError('Wrong input type!')
     fftw = PLANS.lookup_fftw('rfftn', a, s, axes, NTHREADS)
     if fftw is None:
-        fftw = pyfftw.builders.rfftn(a, s, axes)
+        fftw = pyfftw.builders.rfftn(a, s, axes, threads=NTHREADS)
         PLANS.add_fftw('rfftn', fftw, s, axes, NTHREADS)
     return fftw(a).copy()
 
@@ -128,7 +143,7 @@ def _irfftn_fftw(a, s=None, axes=None):
         raise TypeError('Wrong input type!')
     fftw = PLANS.lookup_fftw('irfftn', a, s, axes, NTHREADS)
     if fftw is None:
-        fftw = pyfftw.builders.irfftn(a, s, axes)
+        fftw = pyfftw.builders.irfftn(a, s, axes, threads=NTHREADS)
         PLANS.add_fftw('irfftn', fftw, s, axes, NTHREADS)
     return fftw(a).copy()
 
@@ -154,14 +169,36 @@ def _irfftn_adj_fftw(a):
 
 # These wisdom functions do nothing if pyFFTW is not available
 def dump_wisdom(fname):
-    # TODO: Docstring!
+    '''Wrapper function for the pyfftw.export_wisdom(), which uses a pickle dump.
+
+    Parameters
+    ----------
+    fname: string
+        Name of the file in which the wisdom is saved.
+
+    Returns
+    -------
+    None
+
+    '''
     if pyfftw is not None:
         with open(fname, 'wb') as fp:
             pickle.dump(pyfftw.export_wisdom(), fp, pickle.HIGHEST_PROTOCOL)
 
 
 def load_wisdom(fname):
-    # TODO: Docstring!
+    '''Wrapper function for the pyfftw.import_wisdom(), which uses a pickle to load a file.
+
+    Parameters
+    ----------
+    fname: string
+        Name of the file from which the wisdom is loaded.
+
+    Returns
+    -------
+    None
+
+    '''
     if pyfftw is not None:
         if not os.path.exists(fname):
             print("Warning: Wisdom file does not exist. First time use?")
@@ -172,7 +209,21 @@ def load_wisdom(fname):
 
 # Array setups:
 def empty(shape, dtype=FLOAT):
-    # TODO: Docstring!
+    '''Return a new array of given shape and type without initializing entries.
+
+    Parameters
+    ----------
+    shape: int or tuple of int
+        Shape of the array.
+    dtype: data-type, optional
+        Desired output data-type.
+
+    Returns
+    -------
+    out: :class:`~numpy.ndarray`
+        The created array.
+
+    '''
     result = np.empty(shape, dtype)
     if pyfftw is not None:
         result = pyfftw.n_byte_align(result, pyfftw.simd_alignment)
@@ -180,7 +231,21 @@ def empty(shape, dtype=FLOAT):
 
 
 def zeros(shape, dtype=FLOAT):
-    # TODO: Docstring!
+    '''Return a new array of given shape and type, filled with zeros.
+
+    Parameters
+    ----------
+    shape: int or tuple of int
+        Shape of the array.
+    dtype: data-type, optional
+        Desired output data-type.
+
+    Returns
+    -------
+    out: :class:`~numpy.ndarray`
+        The created array.
+
+    '''
     result = np.zeros(shape, dtype)
     if pyfftw is not None:
         result = pyfftw.n_byte_align(result, pyfftw.simd_alignment)
@@ -188,7 +253,21 @@ def zeros(shape, dtype=FLOAT):
 
 
 def ones(shape, dtype=FLOAT):
-    # TODO: Docstring!
+    '''Return a new array of given shape and type, filled with ones.
+
+    Parameters
+    ----------
+    shape: int or tuple of int
+        Shape of the array.
+    dtype: data-type, optional
+        Desired output data-type.
+
+    Returns
+    -------
+    out: :class:`~numpy.ndarray`
+        The created array.
+
+    '''
     result = np.ones(shape, dtype)
     if pyfftw is not None:
         result = pyfftw.n_byte_align(result, pyfftw.simd_alignment)
@@ -197,11 +276,18 @@ def ones(shape, dtype=FLOAT):
 
 # Configure backend:
 def configure_backend(backend):
-    """
-    Change FFT backend.
+    '''Change FFT backend.
+
+    Parameters
+    ----------
+    backend: string
+        Backend to use. Supported values are "numpy" and "fftw".
+
+    Returns
+    -------
+    None
 
-    Supported values are "numpy" and "fftw".
-    """
+    '''
     global fftn
     global ifftn
     global rfftn
diff --git a/pyramid/forwardmodel.py b/pyramid/forwardmodel.py
index f2758b9..d7f0e6e 100644
--- a/pyramid/forwardmodel.py
+++ b/pyramid/forwardmodel.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """This module provides the :class:`~.ForwardModel` class which represents a strategy to map a
 threedimensional magnetization distribution onto a two-dimensional phase map."""
 
@@ -65,7 +68,6 @@ class ForwardModel(object):
         self._log.debug('Calling __call__')
         self.mag_data.magnitude[:] = 0
         self.mag_data.set_vector(x, self.data_set.mask)
-        # TODO: Multiprocessing
         result = np.zeros(self.m)
         hp = self.hook_points
         for i, projector in enumerate(self.data_set.projectors):
diff --git a/pyramid/kernel.py b/pyramid/kernel.py
index 8c322c5..b8bc451 100644
--- a/pyramid/kernel.py
+++ b/pyramid/kernel.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """This module provides the :class:`~.Kernel` class, representing the phase contribution of one
 single magnetized pixel."""
 
@@ -34,6 +37,14 @@ class Kernel(object):
     dim_uv : tuple of int (N=2), optional
         Dimensions of the 2-dimensional projected magnetization grid from which the phase should
         be calculated.
+    dim_kern : tuple of int (N=2)
+        Dimensions of the kernel, which is ``2N-1`` for both axes compared to `dim_uv`.
+    dim_pad : tuple of int (N=2)
+        Dimensions of the padded FOV, which is ``2N`` (if FFTW is used) or the next highest power
+        of 2 (for numpy-FFT).
+    dim_fft : tuple of int (N=2)
+        Dimensions of the grid, which is used for the FFT, taking into account that a RFFT should
+        be used (one axis is halved in comparison to `dim_pad`).
     b_0 : float, optional
         Saturation magnetization in Tesla, which is used for the phase calculation. Default is 1.
     geometry : {'disc', 'slab'}, optional
@@ -46,20 +57,19 @@ class Kernel(object):
         The real FFT of the phase contribution of one pixel magnetized in u-direction.
     v_fft : :class:`~numpy.ndarray` (N=3)
         The real FFT of the phase contribution of one pixel magnetized in v-direction.
-    dim_fft : tuple of int (N=2)
-        Dimensions of the grid, which is used for the FFT. Calculated by adding the dimensions
-        `dim_uv` of the magnetization grid and the dimensions of the kernel (given by
-        ``2*dim_uv-1``)
-        and increasing to the next multiple of 2 (for faster FFT).
-    slice_fft : tuple (N=2) of :class:`slice`
-        A tuple of :class:`slice` objects to extract the original field of view from the increased
-        size (`size_fft`) of the grid for the FFT-convolution.
+    slice_phase : tuple (N=2) of :class:`slice`
+        A tuple of :class:`slice` objects to extract the original FOV from the increased one with
+        size `dim_pad` for the elementary kernel phase. The kernel is shifted, thus the center is
+        not at (0, 0), which also shifts the slicing compared to `slice_mag`.
+    slice_mag : tuple (N=2) of :class:`slice`
+        A tuple of :class:`slice` objects to extract the original FOV from the increased one with
+        size `dim_pad` for the projected magnetization distribution.
 
-    '''  # TODO: overview what all dim_??? mean! and use_fftw, slice(_fft), etc.
+    '''
 
     _log = logging.getLogger(__name__+'.Kernel')
 
-    def __init__(self, a, dim_uv, b_0=1., geometry='disc', threads=1):
+    def __init__(self, a, dim_uv, b_0=1., geometry='disc'):
         self._log.debug('Calling __init__')
         # Set basic properties:
         self.dim_uv = dim_uv  # Dimensions of the FOV
@@ -76,7 +86,6 @@ class Kernel(object):
                             slice(dim_uv[1]-1, self.dim_kern[1]))  # is not at (0, 0)!
         self.slice_mag = (slice(0, dim_uv[0]),  # Magnetization is padded on the far end!
                           slice(0, dim_uv[1]))  # (Phase cutout is shifted as listed above)
-        self.threads = threads  # TODO: make obsolete!
         # Calculate kernel (single pixel phase):
         coeff = b_0 * a**2 / (2*PHI_0)   # Minus is gone because of negative z-direction
         v_dim, u_dim = dim_uv
@@ -116,7 +125,17 @@ class Kernel(object):
                           - F_a(n-0.5, m+0.5) + F_a(n+0.5, m+0.5))
 
     def print_info(self):
-        # TODO: Docstring!
+        '''Print information about the kernel.
+
+        Parameters
+        ----------
+        None
+
+        Returns
+        -------
+        None
+
+        '''
         self._log.debug('Calling print_info')
         print 'Shape of the FOV   :', self.dim_uv
         print 'Shape of the Kernel:', self.dim_kern
@@ -126,4 +145,3 @@ class Kernel(object):
         print 'Slice for the magn.:', self.slice_mag
         print 'Grid spacing: {} nm'.format(self.a)
         print 'Geometry:', self.geometry
-        print 'Use FFTW: {}; with {} thread(s)'.format(self.use_fftw, self.threads)
diff --git a/pyramid/magcreator.py b/pyramid/magcreator.py
index c3abb46..3083d45 100644
--- a/pyramid/magcreator.py
+++ b/pyramid/magcreator.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """Create simple magnetic distributions.
 
 The :mod:`~.magcreator` module is responsible for the creation of simple distributions of
@@ -165,7 +168,7 @@ class Shapes(object):
         elif axis == 'x':
             mag_shape = np.array([[[np.hypot((y-center[1])/(width[1]/2.),
                                              (z-center[0])/(width[0]/2.)) <= 1
-                                 and abs(z - center[0]) <= height / 2
+                                 and abs(x - center[2]) <= height / 2
                                  for x in range(dim[2])]
                                  for y in range(dim[1])]
                                  for z in range(dim[0])])
@@ -225,9 +228,9 @@ class Shapes(object):
         assert np.shape(dim) == (3,), 'Parameter dim has to be a a tuple of length 3!'
         assert np.shape(center) == (3,), 'Parameter center has to be a a tuple of length 3!'
         assert np.shape(width) == (3,), 'Parameter width has to be a a tuple of length 3!'
-        mag_shape = np.array([[[np.sqrt((x-center[2])**2/(width[2]/2)**2
-                                        + (y-center[1])**2/(width[1]/2)**2
-                                        + (z-center[0])**2/(width[0]/2)**2) <= 1
+        mag_shape = np.array([[[(x-center[2])**2/(width[2]/2)**2
+                                + (y-center[1])**2/(width[1]/2)**2
+                                + (z-center[0])**2/(width[0]/2)**2 <= 1
                              for x in range(dim[2])]
                              for y in range(dim[1])]
                              for z in range(dim[0])])
@@ -388,6 +391,3 @@ def create_mag_dist_vortex(mag_shape, center=None, axis='z', magnitude=1):
         y_mag = -np.ones(dim) * np.cos(phi) * mag_shape * magnitude
         x_mag = np.zeros(dim)
     return np.array([x_mag, y_mag, z_mag])
-
-# TODO: Smooth Vortex!
-# m(x,y) = (-y/r cos(phi(r/R)), x/r sin(phi(r/R)), cos(phi(r))); phi in range[0, 1]
\ No newline at end of file
diff --git a/pyramid/magdata.py b/pyramid/magdata.py
index 7fe3e6c..53f4d43 100644
--- a/pyramid/magdata.py
+++ b/pyramid/magdata.py
@@ -1,640 +1,638 @@
-# -*- coding: utf-8 -*-
-"""This module provides the :class:`~.MagData` class for storing of magnetization data."""
-
-
-import os
-
-import numpy as np
-from numpy.linalg import norm
-from scipy.ndimage.interpolation import zoom
-
-import matplotlib.pyplot as plt
-import matplotlib.cm as cmx
-from matplotlib.ticker import MaxNLocator
-
-from numbers import Number
-
-import netCDF4
-
-import logging
-
-
-__all__ = ['MagData']
-
-
-class MagData(object):
-
-    '''Class for storing magnetization data.
-
-    Represents 3-dimensional magnetic distributions with 3 components which are stored as a
-    2-dimensional numpy array in `magnitude`, but which can also be accessed as a vector via
-    `mag_vec`. :class:`~.MagData` objects support negation, arithmetic operators
-    (``+``, ``-``, ``*``) and their augmented counterparts (``+=``, ``-=``, ``*=``), with numbers
-    and other :class:`~.MagData` objects, if their dimensions and grid spacings match. It is
-    possible to load data from NetCDF4 or LLG (.txt) files or to save the data in these formats.
-    Plotting methods are also provided.
-
-    Attributes
-    ----------
-    a: float
-        The grid spacing in nm.
-    dim: tuple (N=3)
-        Dimensions (z, y, x) of the grid.
-    magnitude: :class:`~numpy.ndarray` (N=4)
-        The `x`-, `y`- and `z`-component of the magnetization vector for every 3D-gridpoint
-        as a 4-dimensional numpy array (first dimension has to be 3, because of the 3 components).
-    mag_vec: :class:`~numpy.ndarray` (N=1)
-        Vector containing the magnetic distribution.
-
-    '''
-
-    _log = logging.getLogger(__name__+'.MagData')
-
-    @property
-    def a(self):
-        return self._a
-
-    @a.setter
-    def a(self, a):
-        assert isinstance(a, Number), 'Grid spacing has to be a number!'
-        assert a >= 0, 'Grid spacing has to be a positive number!'
-        self._a = float(a)
-
-    @property
-    def dim(self):
-        return self._dim
-
-    @property
-    def magnitude(self):
-        return self._magnitude
-
-    @magnitude.setter
-    def magnitude(self, magnitude):
-        assert isinstance(magnitude, np.ndarray), 'Magnitude has to be a numpy array!'
-        assert len(magnitude.shape) == 4, 'Magnitude has to be 4-dimensional!'
-        assert magnitude.shape[0] == 3, 'First dimension of the magnitude has to be 3!'
-        self._magnitude = np.asarray(magnitude, dtype=np.float32)
-        self._dim = magnitude.shape[1:]
-
-    @property
-    def mag_vec(self):
-        return np.reshape(self.magnitude, -1)
-
-    @mag_vec.setter
-    def mag_vec(self, mag_vec):
-        assert isinstance(mag_vec, np.ndarray), 'Vector has to be a numpy array!'
-        assert np.size(mag_vec) == 3*np.prod(self.dim), \
-            'Vector has to match magnitude dimensions! {} {}'.format(mag_vec.shape,
-                                                                     3*np.prod(self.dim))
-        self.magnitude = mag_vec.reshape((3,)+self.dim)
-
-    def __init__(self, a, magnitude):
-        self._log.debug('Calling __init__')
-        self.a = a
-        self.magnitude = magnitude
-        self._log.debug('Created '+str(self))
-
-    def __repr__(self):
-        self._log.debug('Calling __repr__')
-        return '%s(a=%r, magnitude=%r)' % (self.__class__, self.a, self.magnitude)
-
-    def __str__(self):
-        self._log.debug('Calling __str__')
-        return 'MagData(a=%s, dim=%s)' % (self.a, self.dim)
-
-    def __neg__(self):  # -self
-        self._log.debug('Calling __neg__')
-        return MagData(self.a, -self.magnitude)
-
-    def __add__(self, other):  # self + other
-        self._log.debug('Calling __add__')
-        assert isinstance(other, (MagData, Number)), \
-            'Only MagData objects and scalar numbers (as offsets) can be added/subtracted!'
-        if isinstance(other, MagData):
-            self._log.debug('Adding two MagData objects')
-            assert other.a == self.a, 'Added phase has to have the same grid spacing!'
-            assert other.magnitude.shape == (3,)+self.dim, \
-                'Added magnitude has to have the same dimensions!'
-            return MagData(self.a, self.magnitude+other.magnitude)
-        else:  # other is a Number
-            self._log.debug('Adding an offset')
-            return MagData(self.a, self.magnitude+other)
-
-    def __sub__(self, other):  # self - other
-        self._log.debug('Calling __sub__')
-        return self.__add__(-other)
-
-    def __mul__(self, other):  # self * other
-        self._log.debug('Calling __mul__')
-        assert isinstance(other, Number), 'MagData objects can only be multiplied by numbers!'
-        return MagData(self.a, other*self.magnitude)
-
-    def __radd__(self, other):  # other + self
-        self._log.debug('Calling __radd__')
-        return self.__add__(other)
-
-    def __rsub__(self, other):  # other - self
-        self._log.debug('Calling __rsub__')
-        return -self.__sub__(other)
-
-    def __rmul__(self, other):  # other * self
-        self._log.debug('Calling __rmul__')
-        return self.__mul__(other)
-
-    def __iadd__(self, other):  # self += other
-        self._log.debug('Calling __iadd__')
-        return self.__add__(other)
-
-    def __isub__(self, other):  # self -= other
-        self._log.debug('Calling __isub__')
-        return self.__sub__(other)
-
-    def __imul__(self, other):  # self *= other
-        self._log.debug('Calling __imul__')
-        return self.__mul__(other)
-
-    def copy(self):
-        '''Returns a copy of the :class:`~.MagData` object
-
-        Parameters
-        ----------
-        None
-
-        Returns
-        -------
-        mag_data: :class:`~.MagData`
-            A copy of the :class:`~.MagData`.
-
-        '''
-        self._log.debug('Calling copy')
-        return MagData(self.a, self.magnitude.copy())
-
-    def scale_down(self, n=1):
-        '''Scale down the magnetic distribution by averaging over two pixels along each axis.
-
-        Parameters
-        ----------
-        n : int, optional
-            Number of times the magnetic distribution is scaled down. The default is 1.
-
-        Returns
-        -------
-        None
-
-        Notes
-        -----
-        Acts in place and changes dimensions and grid spacing accordingly.
-        Only possible, if each axis length is a power of 2!
-
-        '''
-        self._log.debug('Calling scale_down')
-        assert n > 0 and isinstance(n, (int, long)), 'n must be a positive integer!'
-        self.a = self.a * 2**n
-        for t in range(n):
-            # Pad if necessary:
-            pz, py, px = self.dim[0] % 2, self.dim[1] % 2, self.dim[2] % 2
-            if pz != 0 or py != 0 or px != 0:
-                self.magnitude = np.pad(self.magnitude, ((0, 0), (0, pz), (0, py), (0, px)),
-                                        mode='constant')
-            # Create coarser grid for the magnetization:
-            self.magnitude = self.magnitude.reshape(
-                3, self.dim[0]/2, 2, self.dim[1]/2, 2, self.dim[2]/2, 2).mean(axis=(6, 4, 2))
-
-    def scale_up(self, n=1, order=0):
-        '''Scale up the magnetic distribution using spline interpolation of the requested order.
-
-        Parameters
-        ----------
-        n : int, optional
-            Power of 2 with which the grid is scaled. Default is 1, which means every axis is
-            increased by a factor of ``2**1 = 2``.
-        order : int, optional
-            The order of the spline interpolation, which has to be in the range between 0 and 5
-            and defaults to 0.
-
-        Returns
-        -------
-        None
-
-        Notes
-        -----
-        Acts in place and changes dimensions and grid spacing accordingly.
-        '''
-        self._log.debug('Calling scale_up')
-        assert n > 0 and isinstance(n, (int, long)), 'n must be a positive integer!'
-        assert 5 > order >= 0 and isinstance(order, (int, long)), \
-            'order must be a positive integer between 0 and 5!'
-        self.a = self.a / 2**n
-        self.magnitude = np.array((zoom(self.magnitude[0], zoom=2**n, order=order),
-                                   zoom(self.magnitude[1], zoom=2**n, order=order),
-                                   zoom(self.magnitude[2], zoom=2**n, order=order)))
-
-    def pad(self, x_pad, y_pad, z_pad):
-        '''Pad the current magnetic distribution with zeros for each individual axis.
-
-        Parameters
-        ----------
-        x_pad : int
-            Number of zeros which should be padded on both sides of the x-axis.
-        y_pad : int
-            Number of zeros which should be padded on both sides of the y-axis.
-        z_pad : int
-            Number of zeros which should be padded on both sides of the z-axis.
-
-        Returns
-        -------
-        None
-
-        Notes
-        -----
-        Acts in place and changes dimensions accordingly.
-        '''
-        assert x_pad >= 0 and isinstance(x_pad, (int, long)), 'x_pad must be a positive integer!'
-        assert y_pad >= 0 and isinstance(y_pad, (int, long)), 'y_pad must be a positive integer!'
-        assert z_pad >= 0 and isinstance(z_pad, (int, long)), 'z_pad must be a positive integer!'
-        self.magnitude = np.pad(self.magnitude,
-                                ((0, 0), (z_pad, z_pad), (y_pad, y_pad), (x_pad, x_pad)),
-                                mode='constant')
-
-    def get_mask(self, threshold=0):
-        '''Mask all pixels where the amplitude of the magnetization lies above `threshold`.
-
-        Parameters
-        ----------
-        threshold : float, optional
-            A pixel only gets masked, if it lies above this threshold . The default is 0.
-
-        Returns
-        -------
-        mask : :class:`~numpy.ndarray` (N=3, boolean)
-            Mask of the pixels where the amplitude of the magnetization lies above `threshold`.
-
-        '''
-        return np.sqrt(np.sum(np.array(self.magnitude)**2, axis=0)) > threshold
-
-    def get_vector(self, mask):
-        '''Returns the magnetic components arranged in a vector, specified by a mask.
-
-        Parameters
-        ----------
-        mask : :class:`~numpy.ndarray` (N=3, boolean)
-            Masks the pixels from which the components should be taken.
-
-        Returns
-        -------
-        vector : :class:`~numpy.ndarray` (N=1)
-            The vector containing magnetization components of the specified pixels.
-            Order is: first all `x`-, then all `y`-, then all `z`-components.
-
-        '''
-        if mask is not None:
-            return np.reshape([self.magnitude[0][mask],
-                           self.magnitude[1][mask],
-                           self.magnitude[2][mask]], -1)
-        else:
-            return self.mag_vec
-
-
-    def set_vector(self, vector, mask=None):
-        '''Set the magnetic components of the masked pixels to the values specified by `vector`.
-
-        Parameters
-        ----------
-        mask : :class:`~numpy.ndarray` (N=3, boolean), optional
-            Masks the pixels from which the components should be taken.
-        vector : :class:`~numpy.ndarray` (N=1)
-            The vector containing magnetization components of the specified pixels.
-            Order is: first all `x`-, then all `y-, then all `z`-components.
-
-        Returns
-        -------
-        None
-
-        '''
-        assert np.size(vector) % 3 == 0, 'Vector has to contain all 3 components for every pixel!'
-        count = np.size(vector)/3
-        if mask is not None:
-            self.magnitude[0][mask] = vector[:count]  # x-component
-            self.magnitude[1][mask] = vector[count:2*count]  # y-component
-            self.magnitude[2][mask] = vector[2*count:]  # z-component
-        else:
-            self.mag_vec = vector
-
-    def save_to_llg(self, filename='..\output\magdata_output.txt'):
-        '''Save magnetization data in a file with LLG-format.
-
-        Parameters
-        ----------
-        filename : string, optional
-            The name of the LLG-file in which to store the magnetization data.
-            The default is '..\output\magdata_output.txt'.
-
-        Returns
-        -------
-        None
-
-        '''
-        self._log.debug('Calling save_to_llg')
-        a = self.a * 1.0E-9 / 1.0E-2  # from nm to cm
-        # Create 3D meshgrid and reshape it and the magnetization into a list where x varies first:
-        zz, yy, xx = np.mgrid[a/2:(self.dim[0]*a-a/2):self.dim[0]*1j,
-                              a/2:(self.dim[1]*a-a/2):self.dim[1]*1j,
-                              a/2:(self.dim[2]*a-a/2):self.dim[2]*1j].reshape(3, -1)
-        x_vec, y_vec, z_vec = self.magnitude.reshape(3, -1)
-        # Save data to file:
-        data = np.array([xx, yy, zz, x_vec, y_vec, z_vec]).T
-        with open(filename, 'w') as mag_file:
-            mag_file.write('LLGFileCreator: %s\n' % filename.replace('.txt', ''))
-            mag_file.write('    %d    %d    %d\n' % (self.dim[2], self.dim[1], self.dim[0]))
-            mag_file.writelines('\n'.join('   '.join('{:7.6e}'.format(cell)
-                                          for cell in row) for row in data))
-
-    @classmethod
-    def load_from_llg(cls, filename):
-        '''Construct :class:`~.MagData` object from LLG-file.
-
-        Parameters
-        ----------
-        filename : string
-            The name of the LLG-file from which to load the data.
-
-        Returns
-        -------
-        mag_data: :class:`~.MagData`
-            A :class:`~.MagData` object containing the loaded data.
-
-        '''
-        cls._log.debug('Calling load_from_llg')
-        SCALE = 1.0E-9 / 1.0E-2  # From cm to nm
-        data = np.genfromtxt(filename, skip_header=2)
-        dim = tuple(np.genfromtxt(filename, dtype=int, skip_header=1, skip_footer=len(data[:, 0])))
-        a = (data[1, 0] - data[0, 0]) / SCALE
-        magnitude = data[:, 3:6].T.reshape((3,)+dim)
-        return MagData(a, magnitude)
-
-    def save_to_netcdf4(self, filename='..\output\magdata_output.nc'):
-        '''Save magnetization data in a file with NetCDF4-format.
-
-        Parameters
-        ----------
-        filename : string, optional
-            The name of the NetCDF4-file in which to store the magnetization data.
-            The default is '..\output\magdata_output.nc'.
-
-        Returns
-        -------
-        None
-
-        '''
-        self._log.debug('Calling save_to_netcdf4')
-        mag_file = netCDF4.Dataset(filename, 'w', format='NETCDF4')
-        mag_file.a = self.a
-        mag_file.createDimension('comp', 3)  # Number of components
-        mag_file.createDimension('z_dim', self.dim[0])
-        mag_file.createDimension('y_dim', self.dim[1])
-        mag_file.createDimension('x_dim', self.dim[2])
-        magnitude = mag_file.createVariable('magnitude', 'f', ('comp', 'z_dim', 'y_dim', 'x_dim'))
-        magnitude[...] = self.magnitude
-        mag_file.close()
-
-    @classmethod
-    def load_from_netcdf4(cls, filename):
-        '''Construct :class:`~.DataMag` object from NetCDF4-file.
-
-        Parameters
-        ----------
-        filename : string
-            The name of the NetCDF4-file from which to load the data. Standard format is '\*.nc'.
-
-        Returns
-        -------
-        mag_data: :class:`~.MagData`
-            A :class:`~.MagData` object containing the loaded data.
-
-        '''
-        cls._log.debug('Calling copy')
-        mag_file = netCDF4.Dataset(filename, 'r', format='NETCDF4')
-        a = mag_file.a
-        magnitude = mag_file.variables['magnitude'][...]
-        mag_file.close()
-        return MagData(a, magnitude)
-
-    def quiver_plot(self, title='Magnetization Distribution', axis=None, proj_axis='z',
-                    ar_dens=1, ax_slice=None, log=False, scaled=True):  # TODO: Doc ar_dens
-        '''Plot a slice of the magnetization as a quiver plot.
-
-        Parameters
-        ----------
-        title : string, optional
-            The title for the plot.
-        axis : :class:`~matplotlib.axes.AxesSubplot`, optional
-            Axis on which the graph is plotted. Creates a new figure if none is specified.
-        proj_axis : {'z', 'y', 'x'}, optional
-            The axis, from which a slice is plotted. The default is 'z'.
-        ax_slice : int, optional
-            The slice-index of the axis specified in `proj_axis`. Is set to the center of
-            `proj_axis` if not specified.
-        log : boolean, optional
-            Takes the Default is False.
-        scaled : boolean, optional
-            Normalizes the plotted arrows in respect to the highest one. Default is True.
-
-        Returns
-        -------
-        axis: :class:`~matplotlib.axes.AxesSubplot`
-            The axis on which the graph is plotted.
-
-        '''
-        self._log.debug('Calling quiver_plot')
-        assert proj_axis == 'z' or proj_axis == 'y' or proj_axis == 'x', \
-            'Axis has to be x, y or z (as string).'
-        if proj_axis == 'z':  # Slice of the xy-plane with z = ax_slice
-            self._log.debug('proj_axis == z')
-            if ax_slice is None:
-                self._log.debug('ax_slice is None')
-                ax_slice = int(self.dim[0]/2.)
-            plot_u = np.copy(self.magnitude[0][ax_slice, ...])  # x-component
-            plot_v = np.copy(self.magnitude[1][ax_slice, ...])  # y-component
-            u_label = 'x [px]'
-            v_label = 'y [px]'
-        elif proj_axis == 'y':  # Slice of the xz-plane with y = ax_slice
-            self._log.debug('proj_axis == y')
-            if ax_slice is None:
-                self._log.debug('ax_slice is None')
-                ax_slice = int(self.dim[1]/2.)
-            plot_u = np.copy(self.magnitude[0][:, ax_slice, :])  # x-component
-            plot_v = np.copy(self.magnitude[2][:, ax_slice, :])  # z-component
-            u_label = 'x [px]'
-            v_label = 'z [px]'
-        elif proj_axis == 'x':  # Slice of the yz-plane with x = ax_slice
-            self._log.debug('proj_axis == x')
-            if ax_slice is None:
-                self._log.debug('ax_slice is None')
-                ax_slice = int(self.dim[2]/2.)
-            plot_u = np.swapaxes(np.copy(self.magnitude[2][..., ax_slice]), 0, 1)  # z-component
-            plot_v = np.swapaxes(np.copy(self.magnitude[1][..., ax_slice]), 0, 1)  # y-component
-            u_label = 'z [px]'
-            v_label = 'y [px]'
-        # If no axis is specified, a new figure is created:
-        if axis is None:
-            self._log.debug('axis is None')
-            fig = plt.figure(figsize=(8.5, 7))
-            axis = fig.add_subplot(1, 1, 1)
-        axis.set_aspect('equal')
-        angles = np.angle(plot_u+1j*plot_v, deg=True)
-        # Take the logarithm of the arrows to clearly show directions (if specified):
-        if log:
-            cutoff = 10
-            amp = np.round(np.hypot(plot_u, plot_v), decimals=cutoff)
-            min_value = amp[np.nonzero(amp)].min()
-            plot_u = np.round(plot_u, decimals=cutoff) / min_value
-            plot_u = np.log10(np.abs(plot_u)+1) * np.sign(plot_u)
-            plot_v = np.round(plot_v, decimals=cutoff) / min_value
-            plot_v = np.log10(np.abs(plot_v)+1) * np.sign(plot_v)
-        # Scale the magnitude of the arrows to the highest one (if specified):
-        if scaled:
-            plot_u /= np.hypot(plot_u, plot_v).max()
-            plot_v /= np.hypot(plot_u, plot_v).max()
-        # Setup quiver:
-        dim_uv = plot_u.shape
-        ad = ar_dens
-        xx, yy = np.meshgrid(np.arange(dim_uv[1]), np.arange(dim_uv[0]))
-        axis.quiver(xx[::ad, ::ad], yy[::ad, ::ad], plot_u[::ad, ::ad], plot_v[::ad, ::ad],
-                    pivot='middle', units='xy', angles=angles[::ad, ::ad], minlength=0.25,
-                    scale_units='xy', scale=1./ad, headwidth=6, headlength=7)
-        axis.set_xlim(-1, dim_uv[1])
-        axis.set_ylim(-1, dim_uv[0])
-        axis.set_title(title, fontsize=18)
-        axis.set_xlabel(u_label, fontsize=15)
-        axis.set_ylabel(v_label, fontsize=15)
-        axis.tick_params(axis='both', which='major', labelsize=14)
-        axis.xaxis.set_major_locator(MaxNLocator(nbins=9, integer=True))
-        axis.yaxis.set_major_locator(MaxNLocator(nbins=9, integer=True))
-        return axis
-
-    # TODO: Switch with mayavi or combine
-    def quiver_plot3d_matplotlib(self, title='Magnetization Distribution', ar_dens=1):  # TODO: Doc ar_dens
-        from mpl_toolkits.mplot3d import axes3d  #analysis:ignore
-        # TODO: more arguments like in the other plots and document!!!
-        a = self.a
-        dim = self.dim
-        ad = ar_dens
-        # Create points and vector components as lists:
-        zz, yy, xx = np.mgrid[a/2:(dim[0]*a-a/2):dim[0]*1j,
-                              a/2:(dim[1]*a-a/2):dim[1]*1j,
-                              a/2:(dim[2]*a-a/2):dim[2]*1j]
-        xx = xx[::ad, ::ad, ::ad]
-        yy = yy[::ad, ::ad, ::ad]
-        zz = zz[::ad, ::ad, ::ad]
-        x_mag = self.magnitude[0, ::ad, ::ad, ::ad]
-        y_mag = self.magnitude[1, ::ad, ::ad, ::ad]
-        z_mag = self.magnitude[2, ::ad, ::ad, ::ad]
-        # Plot them as vectors:
-        fig = plt.figure(figsize=(8.5, 7))
-        axis = fig.add_subplot(1, 1, 1)
-        axis = fig.gca(projection='3d')
-        axis.quiver(xx, yy, zz, x_mag, y_mag, z_mag)
-        axis.set_title(title, fontsize=18)
-        # TODO: add colorbar!
-#        mlab.colorbar(None, label_fmt='%.2f')
-#        mlab.colorbar(None, orientation='vertical')
-        return axis
-
-    def quiver_plot3d(self, title='Magnetization Distribution', ar_dens=1):  # TODO: Doc ar_dens
-        '''Plot the magnetization as 3D-vectors in a quiverplot.
-
-        Parameters
-        ----------
-        None
-
-        Returns
-        -------
-        None
-
-        '''
-        self._log.debug('Calling quiver_plot3D')
-        from mayavi import mlab
-        a = self.a
-        dim = self.dim
-        ad = ar_dens
-        # Create points and vector components as lists:
-        zz, yy, xx = np.mgrid[a/2:(dim[0]*a-a/2):dim[0]*1j,
-                              a/2:(dim[1]*a-a/2):dim[1]*1j,
-                              a/2:(dim[2]*a-a/2):dim[2]*1j]
-        xx = xx[::ad, ::ad, ::ad].flatten()#reshape(-1)
-        yy = yy[::ad, ::ad, ::ad].flatten()#.reshape(-1)
-        zz = zz[::ad, ::ad, ::ad].flatten()#.reshape(-1)
-        x_mag = self.magnitude[0][::ad, ::ad, ::ad].flatten()#, (-1))
-        y_mag = self.magnitude[1][::ad, ::ad, ::ad].flatten()#, (-1))
-        z_mag = self.magnitude[2][::ad, ::ad, ::ad].flatten()#, (-1))
-        # Plot them as vectors:
-        mlab.figure()
-        plot = mlab.quiver3d(xx, yy, zz, x_mag, y_mag, z_mag, mode='arrow')
-        mlab.outline(plot)
-        mlab.axes(plot)
-        mlab.title(title, height=0.95, size=0.35)
-        mlab.colorbar(None, label_fmt='%.2f')
-        mlab.colorbar(None, orientation='vertical')
-
-    def save_to_x3d(self, filename='..\..\output\magdata_output.x3d', maximum=1):
-        '''Output the magnetization in the .x3d format for the Fraunhofer InstantReality Player.
-
-        Parameters
-        ----------
-        None
-
-        Returns
-        -------
-        None
-
-        '''
-        self._log.debug('Calling save_to_x3d')
-        from lxml import etree
-
-        dim = self.dim
-        # Create points and vector components as lists:
-        zz, yy, xx = np.mgrid[0.5:(dim[0]-0.5):dim[0]*1j,
-                              0.5:(dim[1]-0.5):dim[1]*1j,
-                              0.5:(dim[2]-0.5):dim[2]*1j]
-        xx = xx.reshape(-1)
-        yy = yy.reshape(-1)
-        zz = zz.reshape(-1)
-        x_mag = np.reshape(self.magnitude[0], (-1))
-        y_mag = np.reshape(self.magnitude[1], (-1))
-        z_mag = np.reshape(self.magnitude[2], (-1))
-        # Load template, load tree and write viewpoint information:
-        template = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'template.x3d')
-        parser = etree.XMLParser(remove_blank_text=True)
-        tree = etree.parse(template, parser)
-        scene = tree.find('Scene')
-        etree.SubElement(scene, 'Viewpoint', position='0 0 {}'.format(1.5*dim[0]),
-                         fieldOfView='1')
-        # Write each "spin"-tag separately:
-        for i in range(np.prod(dim)):
-            mag = np.sqrt(x_mag[i]**2+y_mag[i]**2+z_mag[i]**2)
-            if mag != 0:
-                spin_position = (xx[i]-dim[2]/2., yy[i]-dim[1]/2., zz[i]-dim[0]/2.)
-                sx_ref = 0
-                sy_ref = 1
-                sz_ref = 0
-                rot_x = sy_ref*z_mag[i] - sz_ref*y_mag[i]
-                rot_y = sz_ref*x_mag[i] - sx_ref*z_mag[i]
-                rot_z = sx_ref*y_mag[i] - sy_ref*x_mag[i]
-                angle = np.arccos(y_mag[i]/mag)
-                if norm((rot_x, rot_y, rot_z)) < 1E-10:
-                    rot_x, rot_y, rot_z = 1, 0, 0
-                spin_rotation = (rot_x, rot_y, rot_z, angle)
-                spin_color = cmx.RdYlGn(mag/maximum)[:3]
-                spin_scale = (1., 1., 1.)
-                spin = etree.SubElement(scene, 'ProtoInstance',
-                                        DEF='Spin {}'.format(i), name='Spin_Proto')
-                etree.SubElement(spin, 'fieldValue', name='spin_position',
-                                 value='{} {} {}'.format(*spin_position))
-                etree.SubElement(spin, 'fieldValue', name='spin_rotation',
-                                 value='{} {} {} {}'.format(*spin_rotation))
-                etree.SubElement(spin, 'fieldValue', name='spin_color',
-                                 value='{} {} {}'.format(*spin_color))
-                etree.SubElement(spin, 'fieldValue', name='spin_scale',
-                                 value='{} {} {}'.format(*spin_scale))
-        # Write the tree into the file in pretty print format:
-        tree.write(filename, pretty_print=True)
+# -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
+"""This module provides the :class:`~.MagData` class for storing of magnetization data."""
+
+
+import os
+
+import numpy as np
+from numpy.linalg import norm
+from scipy.ndimage.interpolation import zoom
+
+import matplotlib.pyplot as plt
+import matplotlib.cm as cmx
+from matplotlib.ticker import MaxNLocator
+
+import pyramid.fft as fft
+
+from numbers import Number
+
+import netCDF4
+
+import logging
+
+
+__all__ = ['MagData']
+
+
+class MagData(object):
+
+    '''Class for storing magnetization data.
+
+    Represents 3-dimensional magnetic distributions with 3 components which are stored as a
+    2-dimensional numpy array in `magnitude`, but which can also be accessed as a vector via
+    `mag_vec`. :class:`~.MagData` objects support negation, arithmetic operators
+    (``+``, ``-``, ``*``) and their augmented counterparts (``+=``, ``-=``, ``*=``), with numbers
+    and other :class:`~.MagData` objects, if their dimensions and grid spacings match. It is
+    possible to load data from NetCDF4 or LLG (.txt) files or to save the data in these formats.
+    Plotting methods are also provided.
+
+    Attributes
+    ----------
+    a: float
+        The grid spacing in nm.
+    dim: tuple (N=3)
+        Dimensions (z, y, x) of the grid.
+    magnitude: :class:`~numpy.ndarray` (N=4)
+        The `x`-, `y`- and `z`-component of the magnetization vector for every 3D-gridpoint
+        as a 4-dimensional numpy array (first dimension has to be 3, because of the 3 components).
+    mag_amp: :class:`~numpy.ndarray` (N=3)
+        The length (amplitude) of the magnetization vectors as a 3D-array.
+    mag_vec: :class:`~numpy.ndarray` (N=1)
+        Vector containing the magnetic distribution.
+
+    '''
+
+    _log = logging.getLogger(__name__+'.MagData')
+
+    @property
+    def a(self):
+        return self._a
+
+    @a.setter
+    def a(self, a):
+        assert isinstance(a, Number), 'Grid spacing has to be a number!'
+        assert a >= 0, 'Grid spacing has to be a positive number!'
+        self._a = float(a)
+
+    @property
+    def dim(self):
+        return self._dim
+
+    @property
+    def magnitude(self):
+        return self._magnitude
+
+    @magnitude.setter
+    def magnitude(self, magnitude):
+        assert isinstance(magnitude, np.ndarray), 'Magnitude has to be a numpy array!'
+        assert len(magnitude.shape) == 4, 'Magnitude has to be 4-dimensional!'
+        assert magnitude.shape[0] == 3, 'First dimension of the magnitude has to be 3!'
+        self._magnitude = np.asarray(magnitude, dtype=fft.FLOAT)
+        self._dim = magnitude.shape[1:]
+
+    @property
+    def mag_amp(self):
+        return np.sqrt(np.sum(self.magnitude**2, axis=0))
+
+    @property
+    def mag_vec(self):
+        return np.reshape(self.magnitude, -1)
+
+    @mag_vec.setter
+    def mag_vec(self, mag_vec):
+        mag_vec = np.asarray(mag_vec, dtype=fft.FLOAT)
+        assert np.size(mag_vec) == 3*np.prod(self.dim), \
+            'Vector has to match magnitude dimensions! {} {}'.format(mag_vec.shape,
+                                                                     3*np.prod(self.dim))
+        self.magnitude = mag_vec.reshape((3,)+self.dim)
+
+    def __init__(self, a, magnitude):
+        self._log.debug('Calling __init__')
+        self.a = a
+        self.magnitude = magnitude
+        self._log.debug('Created '+str(self))
+
+    def __repr__(self):
+        self._log.debug('Calling __repr__')
+        return '%s(a=%r, magnitude=%r)' % (self.__class__, self.a, self.magnitude)
+
+    def __str__(self):
+        self._log.debug('Calling __str__')
+        return 'MagData(a=%s, dim=%s)' % (self.a, self.dim)
+
+    def __neg__(self):  # -self
+        self._log.debug('Calling __neg__')
+        return MagData(self.a, -self.magnitude)
+
+    def __add__(self, other):  # self + other
+        self._log.debug('Calling __add__')
+        assert isinstance(other, (MagData, Number)), \
+            'Only MagData objects and scalar numbers (as offsets) can be added/subtracted!'
+        if isinstance(other, MagData):
+            self._log.debug('Adding two MagData objects')
+            assert other.a == self.a, 'Added phase has to have the same grid spacing!'
+            assert other.magnitude.shape == (3,)+self.dim, \
+                'Added magnitude has to have the same dimensions!'
+            return MagData(self.a, self.magnitude+other.magnitude)
+        else:  # other is a Number
+            self._log.debug('Adding an offset')
+            return MagData(self.a, self.magnitude+other)
+
+    def __sub__(self, other):  # self - other
+        self._log.debug('Calling __sub__')
+        return self.__add__(-other)
+
+    def __mul__(self, other):  # self * other
+        self._log.debug('Calling __mul__')
+        assert isinstance(other, Number), 'MagData objects can only be multiplied by numbers!'
+        return MagData(self.a, other*self.magnitude)
+
+    def __radd__(self, other):  # other + self
+        self._log.debug('Calling __radd__')
+        return self.__add__(other)
+
+    def __rsub__(self, other):  # other - self
+        self._log.debug('Calling __rsub__')
+        return -self.__sub__(other)
+
+    def __rmul__(self, other):  # other * self
+        self._log.debug('Calling __rmul__')
+        return self.__mul__(other)
+
+    def __iadd__(self, other):  # self += other
+        self._log.debug('Calling __iadd__')
+        return self.__add__(other)
+
+    def __isub__(self, other):  # self -= other
+        self._log.debug('Calling __isub__')
+        return self.__sub__(other)
+
+    def __imul__(self, other):  # self *= other
+        self._log.debug('Calling __imul__')
+        return self.__mul__(other)
+
+    def copy(self):
+        '''Returns a copy of the :class:`~.MagData` object
+
+        Parameters
+        ----------
+        None
+
+        Returns
+        -------
+        mag_data: :class:`~.MagData`
+            A copy of the :class:`~.MagData`.
+
+        '''
+        self._log.debug('Calling copy')
+        return MagData(self.a, self.magnitude.copy())
+
+    def scale_down(self, n=1):
+        '''Scale down the magnetic distribution by averaging over two pixels along each axis.
+
+        Parameters
+        ----------
+        n : int, optional
+            Number of times the magnetic distribution is scaled down. The default is 1.
+
+        Returns
+        -------
+        None
+
+        Notes
+        -----
+        Acts in place and changes dimensions and grid spacing accordingly.
+        Only possible, if each axis length is a power of 2!
+
+        '''
+        self._log.debug('Calling scale_down')
+        assert n > 0 and isinstance(n, (int, long)), 'n must be a positive integer!'
+        self.a = self.a * 2**n
+        for t in range(n):
+            # Pad if necessary:
+            pz, py, px = self.dim[0] % 2, self.dim[1] % 2, self.dim[2] % 2
+            if pz != 0 or py != 0 or px != 0:
+                self.magnitude = np.pad(self.magnitude, ((0, 0), (0, pz), (0, py), (0, px)),
+                                        mode='constant')
+            # Create coarser grid for the magnetization:
+            self.magnitude = self.magnitude.reshape(
+                3, self.dim[0]/2, 2, self.dim[1]/2, 2, self.dim[2]/2, 2).mean(axis=(6, 4, 2))
+
+    def scale_up(self, n=1, order=0):
+        '''Scale up the magnetic distribution using spline interpolation of the requested order.
+
+        Parameters
+        ----------
+        n : int, optional
+            Power of 2 with which the grid is scaled. Default is 1, which means every axis is
+            increased by a factor of ``2**1 = 2``.
+        order : int, optional
+            The order of the spline interpolation, which has to be in the range between 0 and 5
+            and defaults to 0.
+
+        Returns
+        -------
+        None
+
+        Notes
+        -----
+        Acts in place and changes dimensions and grid spacing accordingly.
+        '''
+        self._log.debug('Calling scale_up')
+        assert n > 0 and isinstance(n, (int, long)), 'n must be a positive integer!'
+        assert 5 > order >= 0 and isinstance(order, (int, long)), \
+            'order must be a positive integer between 0 and 5!'
+        self.a = self.a / 2**n
+        self.magnitude = np.array((zoom(self.magnitude[0], zoom=2**n, order=order),
+                                   zoom(self.magnitude[1], zoom=2**n, order=order),
+                                   zoom(self.magnitude[2], zoom=2**n, order=order)))
+
+    def pad(self, x_pad, y_pad, z_pad):
+        '''Pad the current magnetic distribution with zeros for each individual axis.
+
+        Parameters
+        ----------
+        x_pad : int
+            Number of zeros which should be padded on both sides of the x-axis.
+        y_pad : int
+            Number of zeros which should be padded on both sides of the y-axis.
+        z_pad : int
+            Number of zeros which should be padded on both sides of the z-axis.
+
+        Returns
+        -------
+        None
+
+        Notes
+        -----
+        Acts in place and changes dimensions accordingly.
+        '''
+        assert x_pad >= 0 and isinstance(x_pad, (int, long)), 'x_pad must be a positive integer!'
+        assert y_pad >= 0 and isinstance(y_pad, (int, long)), 'y_pad must be a positive integer!'
+        assert z_pad >= 0 and isinstance(z_pad, (int, long)), 'z_pad must be a positive integer!'
+        self.magnitude = np.pad(self.magnitude,
+                                ((0, 0), (z_pad, z_pad), (y_pad, y_pad), (x_pad, x_pad)),
+                                mode='constant')
+
+    def get_mask(self, threshold=0):
+        '''Mask all pixels where the amplitude of the magnetization lies above `threshold`.
+
+        Parameters
+        ----------
+        threshold : float, optional
+            A pixel only gets masked, if it lies above this threshold . The default is 0.
+
+        Returns
+        -------
+        mask : :class:`~numpy.ndarray` (N=3, boolean)
+            Mask of the pixels where the amplitude of the magnetization lies above `threshold`.
+
+        '''
+        return self.mag_amp > threshold
+
+    def get_vector(self, mask):
+        '''Returns the magnetic components arranged in a vector, specified by a mask.
+
+        Parameters
+        ----------
+        mask : :class:`~numpy.ndarray` (N=3, boolean)
+            Masks the pixels from which the components should be taken.
+
+        Returns
+        -------
+        vector : :class:`~numpy.ndarray` (N=1)
+            The vector containing magnetization components of the specified pixels.
+            Order is: first all `x`-, then all `y`-, then all `z`-components.
+
+        '''
+        if mask is not None:
+            return np.reshape([self.magnitude[0][mask],
+                               self.magnitude[1][mask],
+                               self.magnitude[2][mask]], -1)
+        else:
+            return self.mag_vec
+
+    def set_vector(self, vector, mask=None):
+        '''Set the magnetic components of the masked pixels to the values specified by `vector`.
+
+        Parameters
+        ----------
+        mask : :class:`~numpy.ndarray` (N=3, boolean), optional
+            Masks the pixels from which the components should be taken.
+        vector : :class:`~numpy.ndarray` (N=1)
+            The vector containing magnetization components of the specified pixels.
+            Order is: first all `x`-, then all `y-, then all `z`-components.
+
+        Returns
+        -------
+        None
+
+        '''
+        vector = np.asarray(vector, dtype=fft.FLOAT)
+        assert np.size(vector) % 3 == 0, 'Vector has to contain all 3 components for every pixel!'
+        count = np.size(vector)/3
+        if mask is not None:
+            self.magnitude[0][mask] = vector[:count]  # x-component
+            self.magnitude[1][mask] = vector[count:2*count]  # y-component
+            self.magnitude[2][mask] = vector[2*count:]  # z-component
+        else:
+            self.mag_vec = vector
+
+    def save_to_llg(self, filename='..\output\magdata_output.txt'):
+        '''Save magnetization data in a file with LLG-format.
+
+        Parameters
+        ----------
+        filename : string, optional
+            The name of the LLG-file in which to store the magnetization data.
+            The default is '..\output\magdata_output.txt'.
+
+        Returns
+        -------
+        None
+
+        '''
+        self._log.debug('Calling save_to_llg')
+        a = self.a * 1.0E-9 / 1.0E-2  # from nm to cm
+        # Create 3D meshgrid and reshape it and the magnetization into a list where x varies first:
+        zz, yy, xx = (np.indices(self.dim)-a/2).reshape(3, -1)
+        x_vec, y_vec, z_vec = self.magnitude.reshape(3, -1)
+        # Save data to file:
+        data = np.array([xx, yy, zz, x_vec, y_vec, z_vec]).T
+        with open(filename, 'w') as mag_file:
+            mag_file.write('LLGFileCreator: %s\n' % filename.replace('.txt', ''))
+            mag_file.write('    %d    %d    %d\n' % (self.dim[2], self.dim[1], self.dim[0]))
+            mag_file.writelines('\n'.join('   '.join('{:7.6e}'.format(cell)
+                                          for cell in row) for row in data))
+
+    @classmethod
+    def load_from_llg(cls, filename):
+        '''Construct :class:`~.MagData` object from LLG-file.
+
+        Parameters
+        ----------
+        filename : string
+            The name of the LLG-file from which to load the data.
+
+        Returns
+        -------
+        mag_data: :class:`~.MagData`
+            A :class:`~.MagData` object containing the loaded data.
+
+        '''
+        cls._log.debug('Calling load_from_llg')
+        SCALE = 1.0E-9 / 1.0E-2  # From cm to nm
+        data = np.genfromtxt(filename, skip_header=2)
+        dim = tuple(np.genfromtxt(filename, dtype=int, skip_header=1, skip_footer=len(data[:, 0])))
+        a = (data[1, 0] - data[0, 0]) / SCALE
+        magnitude = data[:, 3:6].T.reshape((3,)+dim)
+        return MagData(a, magnitude)
+
+    def save_to_netcdf4(self, filename='..\output\magdata_output.nc'):
+        '''Save magnetization data in a file with NetCDF4-format.
+
+        Parameters
+        ----------
+        filename : string, optional
+            The name of the NetCDF4-file in which to store the magnetization data.
+            The default is '..\output\magdata_output.nc'.
+
+        Returns
+        -------
+        None
+
+        '''
+        self._log.debug('Calling save_to_netcdf4')
+        mag_file = netCDF4.Dataset(filename, 'w', format='NETCDF4')
+        mag_file.a = self.a
+        mag_file.createDimension('comp', 3)  # Number of components
+        mag_file.createDimension('z_dim', self.dim[0])
+        mag_file.createDimension('y_dim', self.dim[1])
+        mag_file.createDimension('x_dim', self.dim[2])
+        magnitude = mag_file.createVariable('magnitude', 'f', ('comp', 'z_dim', 'y_dim', 'x_dim'))
+        magnitude[...] = self.magnitude
+        mag_file.close()
+
+    @classmethod
+    def load_from_netcdf4(cls, filename):
+        '''Construct :class:`~.DataMag` object from NetCDF4-file.
+
+        Parameters
+        ----------
+        filename : string
+            The name of the NetCDF4-file from which to load the data. Standard format is '\*.nc'.
+
+        Returns
+        -------
+        mag_data: :class:`~.MagData`
+            A :class:`~.MagData` object containing the loaded data.
+
+        '''
+        cls._log.debug('Calling copy')
+        mag_file = netCDF4.Dataset(filename, 'r', format='NETCDF4')
+        a = mag_file.a
+        magnitude = mag_file.variables['magnitude'][...]
+        mag_file.close()
+        return MagData(a, magnitude)
+
+    def save_to_x3d(self, filename='..\..\output\magdata_output.x3d', maximum=1):
+        '''Output the magnetization in the .x3d format for the Fraunhofer InstantReality Player.
+
+        Parameters
+        ----------
+        None
+
+        Returns
+        -------
+        None
+
+        '''
+        self._log.debug('Calling save_to_x3d')
+        from lxml import etree
+
+        dim = self.dim
+        # Create points and vector components as lists:
+        zz, yy, xx = (np.indices(dim)-0.5).reshape(3, -1)
+        x_mag = np.reshape(self.magnitude[0], (-1))
+        y_mag = np.reshape(self.magnitude[1], (-1))
+        z_mag = np.reshape(self.magnitude[2], (-1))
+        # Load template, load tree and write viewpoint information:
+        template = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'template.x3d')
+        parser = etree.XMLParser(remove_blank_text=True)
+        tree = etree.parse(template, parser)
+        scene = tree.find('Scene')
+        etree.SubElement(scene, 'Viewpoint', position='0 0 {}'.format(1.5*dim[0]),
+                         fieldOfView='1')
+        # Write each "spin"-tag separately:
+        for i in range(np.prod(dim)):
+            mag = np.sqrt(x_mag[i]**2+y_mag[i]**2+z_mag[i]**2)
+            if mag != 0:
+                spin_position = (xx[i]-dim[2]/2., yy[i]-dim[1]/2., zz[i]-dim[0]/2.)
+                sx_ref = 0
+                sy_ref = 1
+                sz_ref = 0
+                rot_x = sy_ref*z_mag[i] - sz_ref*y_mag[i]
+                rot_y = sz_ref*x_mag[i] - sx_ref*z_mag[i]
+                rot_z = sx_ref*y_mag[i] - sy_ref*x_mag[i]
+                angle = np.arccos(y_mag[i]/mag)
+                if norm((rot_x, rot_y, rot_z)) < 1E-10:
+                    rot_x, rot_y, rot_z = 1, 0, 0
+                spin_rotation = (rot_x, rot_y, rot_z, angle)
+                spin_color = cmx.RdYlGn(mag/maximum)[:3]
+                spin_scale = (1., 1., 1.)
+                spin = etree.SubElement(scene, 'ProtoInstance',
+                                        DEF='Spin {}'.format(i), name='Spin_Proto')
+                etree.SubElement(spin, 'fieldValue', name='spin_position',
+                                 value='{} {} {}'.format(*spin_position))
+                etree.SubElement(spin, 'fieldValue', name='spin_rotation',
+                                 value='{} {} {} {}'.format(*spin_rotation))
+                etree.SubElement(spin, 'fieldValue', name='spin_color',
+                                 value='{} {} {}'.format(*spin_color))
+                etree.SubElement(spin, 'fieldValue', name='spin_scale',
+                                 value='{} {} {}'.format(*spin_scale))
+        # Write the tree into the file in pretty print format:
+        tree.write(filename, pretty_print=True)
+
+    def quiver_plot(self, title='Magnetization Distribution', axis=None, proj_axis='z',
+                    ar_dens=1, ax_slice=None, log=False, scaled=True):
+        '''Plot a slice of the magnetization as a quiver plot.
+
+        Parameters
+        ----------
+        title : string, optional
+            The title for the plot.
+        axis : :class:`~matplotlib.axes.AxesSubplot`, optional
+            Axis on which the graph is plotted. Creates a new figure if none is specified.
+        proj_axis : {'z', 'y', 'x'}, optional
+            The axis, from which a slice is plotted. The default is 'z'.
+        ar_dens: int (optional)
+            Number defining the arrow density which is plotted. A higher ar_dens number skips more
+            arrows (a number of 2 plots every second arrow). Default is 1.
+        ax_slice : int, optional
+            The slice-index of the axis specified in `proj_axis`. Is set to the center of
+            `proj_axis` if not specified.
+        log : boolean, optional
+            Takes the Default is False.
+        scaled : boolean, optional
+            Normalizes the plotted arrows in respect to the highest one. Default is True.
+
+        Returns
+        -------
+        axis: :class:`~matplotlib.axes.AxesSubplot`
+            The axis on which the graph is plotted.
+
+        '''
+        self._log.debug('Calling quiver_plot')
+        assert proj_axis == 'z' or proj_axis == 'y' or proj_axis == 'x', \
+            'Axis has to be x, y or z (as string).'
+        if proj_axis == 'z':  # Slice of the xy-plane with z = ax_slice
+            self._log.debug('proj_axis == z')
+            if ax_slice is None:
+                self._log.debug('ax_slice is None')
+                ax_slice = int(self.dim[0]/2.)
+            plot_u = np.copy(self.magnitude[0][ax_slice, ...])  # x-component
+            plot_v = np.copy(self.magnitude[1][ax_slice, ...])  # y-component
+            u_label = 'x [px]'
+            v_label = 'y [px]'
+        elif proj_axis == 'y':  # Slice of the xz-plane with y = ax_slice
+            self._log.debug('proj_axis == y')
+            if ax_slice is None:
+                self._log.debug('ax_slice is None')
+                ax_slice = int(self.dim[1]/2.)
+            plot_u = np.copy(self.magnitude[0][:, ax_slice, :])  # x-component
+            plot_v = np.copy(self.magnitude[2][:, ax_slice, :])  # z-component
+            u_label = 'x [px]'
+            v_label = 'z [px]'
+        elif proj_axis == 'x':  # Slice of the yz-plane with x = ax_slice
+            self._log.debug('proj_axis == x')
+            if ax_slice is None:
+                self._log.debug('ax_slice is None')
+                ax_slice = int(self.dim[2]/2.)
+            plot_u = np.swapaxes(np.copy(self.magnitude[2][..., ax_slice]), 0, 1)  # z-component
+            plot_v = np.swapaxes(np.copy(self.magnitude[1][..., ax_slice]), 0, 1)  # y-component
+            u_label = 'z [px]'
+            v_label = 'y [px]'
+        # If no axis is specified, a new figure is created:
+        if axis is None:
+            self._log.debug('axis is None')
+            fig = plt.figure(figsize=(8.5, 7))
+            axis = fig.add_subplot(1, 1, 1)
+        axis.set_aspect('equal')
+        angles = np.angle(plot_u+1j*plot_v, deg=True)
+        # Take the logarithm of the arrows to clearly show directions (if specified):
+        if log:
+            cutoff = 10
+            amp = np.round(np.hypot(plot_u, plot_v), decimals=cutoff)
+            min_value = amp[np.nonzero(amp)].min()
+            plot_u = np.round(plot_u, decimals=cutoff) / min_value
+            plot_u = np.log10(np.abs(plot_u)+1) * np.sign(plot_u)
+            plot_v = np.round(plot_v, decimals=cutoff) / min_value
+            plot_v = np.log10(np.abs(plot_v)+1) * np.sign(plot_v)
+        # Scale the magnitude of the arrows to the highest one (if specified):
+        if scaled:
+            plot_u /= np.hypot(plot_u, plot_v).max()
+            plot_v /= np.hypot(plot_u, plot_v).max()
+        # Setup quiver:
+        dim_uv = plot_u.shape
+        ad = ar_dens
+        yy, xx = np.indices(dim_uv)
+        axis.quiver(xx[::ad, ::ad], yy[::ad, ::ad], plot_u[::ad, ::ad], plot_v[::ad, ::ad],
+                    pivot='middle', units='xy', angles=angles[::ad, ::ad], minlength=0.25,
+                    scale_units='xy', scale=1./ad, headwidth=6, headlength=7)
+        axis.set_xlim(-1, dim_uv[1])
+        axis.set_ylim(-1, dim_uv[0])
+        axis.set_title(title, fontsize=18)
+        axis.set_xlabel(u_label, fontsize=15)
+        axis.set_ylabel(v_label, fontsize=15)
+        axis.tick_params(axis='both', which='major', labelsize=14)
+        axis.xaxis.set_major_locator(MaxNLocator(nbins=9, integer=True))
+        axis.yaxis.set_major_locator(MaxNLocator(nbins=9, integer=True))
+        return axis
+
+    def quiver_plot3d(self, title='Magnetization Distribution', limit=None, cmap='cool',
+                      ar_dens=1, mode='arrow', show_pipeline=False):
+        '''Plot the magnetization as 3D-vectors in a quiverplot.
+
+        Parameters
+        ----------
+        title : string, optional
+            The title for the plot.
+        limit : float, optional
+            Plotlimit for the magnetization arrow length used to scale the colormap.
+        cmap : string, optional
+            String describing the colormap which is used (default is 'cool').
+        ar_dens: int (optional)
+            Number defining the arrow density which is plotted. A higher ar_dens number skips more
+            arrows (a number of 2 plots every second arrow). Default is 1.
+        mode: string, optional
+            Mode, determining the glyphs used in the 3D plot. Default is 'arrow', which corresponds
+            to 3D arrows. For large amounts of arrows, '2darrow' should be used.
+        show_pipeline : boolean, optional
+            If True, the mayavi pipeline, a GUI used for image manipulation is shown. The default
+            is False.
+
+        Returns
+        -------
+        plot : :class:`mayavi.modules.vectors.Vectors`
+            The plot object.
+
+        '''
+        self._log.debug('Calling quiver_plot3D')
+        from mayavi import mlab
+        a = self.a
+        dim = self.dim
+        if limit is None:
+            limit = np.max(self.mag_amp)
+        ad = ar_dens
+        # Create points and vector components as lists:
+        zz, yy, xx = (np.indices(dim)-a/2).reshape(3, -1)
+        x_mag = self.magnitude[0][::ad, ::ad, ::ad].flatten()
+        y_mag = self.magnitude[1][::ad, ::ad, ::ad].flatten()
+        z_mag = self.magnitude[2][::ad, ::ad, ::ad].flatten()
+        # Plot them as vectors:
+        mlab.figure()
+        plot = mlab.quiver3d(xx, yy, zz, x_mag, y_mag, z_mag, mode=mode, colormap=cmap)
+        plot.glyph.glyph_source.glyph_position = 'center'
+        plot.module_manager.vector_lut_manager.data_range = np.array([0, limit])
+        mlab.outline(plot)
+        mlab.axes(plot)
+        mlab.title(title, height=0.95, size=0.35)
+        mlab.colorbar(label_fmt='%.2f')
+        mlab.colorbar(orientation='vertical')
+        mlab.orientation_axes()
+        if show_pipeline:
+            mlab.show_pipeline()
+        return plot
diff --git a/pyramid/numcore/phasemapper_core.pyx b/pyramid/numcore/phasemapper_core.pyx
index d7b1f04..59be7c8 100644
--- a/pyramid/numcore/phasemapper_core.pyx
+++ b/pyramid/numcore/phasemapper_core.pyx
@@ -106,7 +106,6 @@ def jac_dot_real_convolve(
                     result[r] -= vector[s+size] * v_phi[v, u]
                     r += 1
             s += 1
-    # TODO: linearize u and v, too?
 
 
 @cython.boundscheck(False)
@@ -155,4 +154,3 @@ def jac_T_dot_real_convolve(
                     result[r+size] -= vector[s] * v_phi[v, u]
                     s += 1
             r += 1
-    # TODO: linearize u and v, too?
diff --git a/pyramid/phasemap.py b/pyramid/phasemap.py
index d60b410..ce5c45a 100644
--- a/pyramid/phasemap.py
+++ b/pyramid/phasemap.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """This module provides the :class:`~.PhaseMap` class for storing phase map data."""
 
 
@@ -323,8 +326,8 @@ class PhaseMap(object):
 
         Returns
         -------
-        axis: :class:`~matplotlib.axes.AxesSubplot`
-            The axis on which the graph is plotted.
+        axis, cbar: :class:`~matplotlib.axes.AxesSubplot`
+            The axis on which the graph is plotted and the colorbar.
 
         '''
         self._log.debug('Calling display_phase')
@@ -359,7 +362,7 @@ class PhaseMap(object):
             cbar.ax.tick_params(labelsize=14)
             cbar.set_label(u'phase shift [{}]'.format(self.unit), fontsize=15)
         # Return plotting axis:
-        return axis
+        return axis, cbar
 
     def display_phase3d(self, title='Phase Map', cmap='RdBu'):
         '''Display the phasemap as a 3-D surface with contourplots.
@@ -385,9 +388,7 @@ class PhaseMap(object):
         fig = plt.figure()
         axis = Axes3D(fig)
         # Plot surface and contours:
-        u = range(self.dim_uv[1])
-        v = range(self.dim_uv[0])
-        uu, vv = np.meshgrid(u, v)
+        vv, uu = np.indices(self.dim_uv)
         axis.plot_surface(uu, vv, phase, rstride=4, cstride=4, alpha=0.7, cmap=cmap,
                           linewidth=0, antialiased=False)
         axis.contourf(uu, vv, phase, 15, zdir='z', offset=np.min(phase), cmap=cmap)
@@ -547,9 +548,7 @@ class PhaseMap(object):
 
         '''
         cls._log.debug('Calling make_color_wheel')
-        x = np.linspace(-256, 256, num=512)
-        y = np.linspace(-256, 256, num=512)
-        xx, yy = np.meshgrid(x, y)
+        yy, xx = np.indices((512, 512)) - 256
         r = np.sqrt(xx ** 2 + yy ** 2)
         # Create the wheel:
         color_wheel_magnitude = (1 - np.cos(r * pi/360)) / 2
diff --git a/pyramid/phasemapper.py b/pyramid/phasemapper.py
index 76fedda..b4c6cab 100644
--- a/pyramid/phasemapper.py
+++ b/pyramid/phasemapper.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """This module executes several forward models to calculate the magnetic or electric phase map from
 a given projection of a 3-dimensional magnetic distribution (see :mod:`~pyramid.projector`).
 For the magnetic phase map, an approach using real space and one using Fourier space is provided.
@@ -431,12 +434,10 @@ class PhaseMapperFDFC(PhaseMapper):
         mag_proj = MagData(self.a, np.zeros((3, 1)+self.dim_uv))
         magnitude_proj = self.jac_dot(vector).reshape((2, )+self.dim_uv)
         mag_proj.magnitude[1:3, 0, ...] = magnitude_proj
-        # TODO: instead call common subroutine operating on u_mag, v_mag with __call__?
         return self(mag_proj).phase_vec
 
     def jac_T_dot(self, vector):
         raise NotImplementedError()
-        # TODO: Implement!
 
 
 class PhaseMapperElectric(PhaseMapper):
@@ -509,11 +510,9 @@ class PhaseMapperElectric(PhaseMapper):
 
     def jac_dot(self, vector):
         raise NotImplementedError()
-        # TODO: Implement?
 
     def jac_T_dot(self, vector):
         raise NotImplementedError()
-        # TODO: Implement?
 
 
 def pm(mag_data, axis='z', dim_uv=None, b_0=1):
diff --git a/pyramid/projector.py b/pyramid/projector.py
index 48e6944..773db4b 100644
--- a/pyramid/projector.py
+++ b/pyramid/projector.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """This module provides the abstract base class :class:`~.Projector` and concrete subclasses for
 projections of vector and scalar fields."""
 
@@ -436,7 +439,7 @@ class SimpleProjector(Projector):
         self._log.debug('Calling __init__')
         assert axis in {'z', 'y', 'x'}, 'Projection axis has to be x, y or z (given as a string)!'
         proj, v, u = self.AXIS_DICT[axis]
-        if axis=='x':
+        if axis == 'x':
             dim_proj, dim_v, dim_u = dim[proj], dim[u], dim[v]  # coordinate switch for 'x'!
         else:
             dim_proj, dim_v, dim_u = dim[proj], dim[v], dim[u]
@@ -453,14 +456,14 @@ class SimpleProjector(Projector):
         elif axis == 'y':
             self._log.debug('Projection along the y-axis')
             coeff = [[1, 0, 0], [0, 0, 1]]
-            indices = np.array([np.arange(row%dim_x, dim_x*dim_y, dim_x)+int(row/dim_x)*dim_x*dim_y
+            indices = np.array([np.arange(row % dim_x, dim_x*dim_y, dim_x) + row//dim_x*dim_x*dim_y
                                 for row in range(size_2d)]).reshape(-1)
         elif axis == 'x':
             self._log.debug('Projection along the x-axis')
             coeff = [[0, 0, 1], [0, 1, 0]]  # Caution, coordinate switch: u, v --> z, y (not y, z!)
             #  indices = np.array([np.arange(dim_proj) + row*dim_proj
             #                      for row in range(size_2d)]).reshape(-1)  # this is u, v --> y, z
-            indices = np.array([np.arange(dim_x) + (row%dim_z)*dim_x*dim_y + int(row/dim_z)*dim_x
+            indices = np.array([np.arange(dim_x) + (row % dim_z)*dim_x*dim_y + row//dim_z*dim_x
                                 for row in range(size_2d)]).reshape(-1)
         if dim_uv is not None:
             indptr = indptr.tolist()  # convert to use insert() and append()
@@ -495,3 +498,5 @@ class SimpleProjector(Projector):
 
         '''
         return 'projected along {}-axis'.format(self.axis)
+
+# TODO: Arbitrary Projections!
diff --git a/pyramid/reconstruction.py b/pyramid/reconstruction.py
index d9386ee..f5df52a 100644
--- a/pyramid/reconstruction.py
+++ b/pyramid/reconstruction.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
 """Reconstruct magnetic distributions from given phasemaps.
 
 This module reconstructs 3-dimensional magnetic distributions (as :class:`~pyramid.magdata.MagData`
@@ -12,77 +15,16 @@ the distribution.
 
 import numpy as np
 
-from pyramid.kernel import Kernel
-from pyramid.projector import SimpleProjector
-from pyramid.phasemapper import PhaseMapperRDFC
 from pyramid.costfunction import Costfunction
 from pyramid.magdata import MagData
 
-from scipy.optimize import leastsq
-
 import logging
 
 
-__all__ = ['optimize_linear', 'optimize_nonlin', 'optimize_splitbregman',
-           'optimize_simple_leastsq']
+__all__ = ['optimize_linear', 'optimize_nonlin', 'optimize_splitbregman']
 _log = logging.getLogger(__name__)
 
 
-class PrintIterator(object):
-
-    '''Iterator class which is responsible to give feedback during reconstruction iterations.
-
-    Parameters
-    ----------
-    cost : :class:`~.Costfunction`
-        :class:`~.Costfunction` class for outputting the `cost` of the current magnetization
-        distribution. This should decrease per iteration if the algorithm converges and is only
-        printed for a `verbosity` of 2.
-    verbosity : {0, 1, 2}, optional
-        Parameter defining the verbosity of the output. `2` will show the current number of the
-        iteration and the cost of the current distribution. `1` will just show the iteration
-        number and `0` will prevent output all together.
-
-    Notes
-    -----
-    Normally this class should not be used by the user and is instantiated whithin the
-    :mod:`~.reconstruction` module itself.
-
-    '''
-
-    _log = logging.getLogger(__name__ + '.PrintIterator')
-
-    def __init__(self, cost, verbosity):
-        self._log.debug('Calling __init__')
-        self.cost = cost
-        self.verbosity = verbosity
-        assert verbosity in {0, 1, 2}, 'verbosity has to be set to 0, 1 or 2!'
-        self.iteration = 0
-        self._log.debug('Created ' + str(self))
-
-    def __call__(self, xk):
-        self._log.debug('Calling __call__')
-        if self.verbosity == 0:
-            return
-        print 'iteration #', self.next(),
-        if self.verbosity > 1:
-            print 'cost =', self.cost(xk)
-        else:
-            print ''
-
-    def __repr__(self):
-        self._log.debug('Calling __repr__')
-        return '%s(cost=%r, verbosity=%r)' % (self.__class__, self.cost, self.verbosity)
-
-    def __str__(self):
-        self._log.debug('Calling __str__')
-        return 'PrintIterator(cost=%s, verbosity=%s)' % (self.cost, self.verbosity)
-
-    def next(self):
-        self.iteration += 1
-        return self.iteration
-
-
 def optimize_linear(data, regularisator=None, max_iter=None):
     '''Reconstruct a three-dimensional magnetic distribution from given phase maps via the
     conjugate gradient optimizaion method :func:`~.scipy.sparse.linalg.cg`.
@@ -103,7 +45,7 @@ def optimize_linear(data, regularisator=None, max_iter=None):
     mag_data : :class:`~pyramid.magdata.MagData`
         The reconstructed magnetic distribution as a :class:`~.MagData` object.
 
-    '''  # TODO: info document!
+    '''
     import jutil.cg as jcg
     _log.debug('Calling optimize_linear')
     # Set up necessary objects:
@@ -128,7 +70,7 @@ def optimize_nonlin(data, first_guess=None, regularisator=None):
         :class:`~.DataSet` object containing all phase maps in :class:`~.PhaseMap` objects and all
         projection directions in :class:`~.Projector` objects. These provide the essential
         information for the reconstruction.
-    first_fuess : :class:`~pyramid.magdata.MagData`
+    first_guess : :class:`~pyramid.magdata.MagData`
         magnetization to start the non-linear iteration with.
     regularisator : :class:`~.Regularisator`, optional
         Regularisator class that's responsible for the regularisation term.
@@ -151,7 +93,6 @@ def optimize_nonlin(data, first_guess=None, regularisator=None):
 
     p = regularisator.p
     q = 1. / (1. - (1. / p))
-    lp = regularisator.norm
     lq = jnorms.LPPow(q, 1e-20)
 
     def preconditioner(_, direc):
@@ -172,6 +113,7 @@ def optimize_nonlin(data, first_guess=None, regularisator=None):
     mag_opt.set_vector(x_opt, data.mask)
     return mag_opt
 
+
 def optimize_splitbregman(data, weight, lam, mu):
     '''
     Reconstructs magnet distribution from phase image measurements using a split bregman
@@ -209,7 +151,6 @@ def optimize_splitbregman(data, weight, lam, mu):
     # function to that which is supposedly optimized by split bregman.
     # Thus cost can be used to verify convergence
     regularisator = FirstOrderRegularisator(data.mask, lam / mu, 1)
-    x_0 = MagData(data.a, np.zeros((3,) + data.dim)).get_vector(data.mask)
     cost = Costfunction(data, regularisator)
     fwd_mod = cost.fwd_model
 
@@ -229,80 +170,3 @@ def optimize_splitbregman(data, weight, lam, mu):
     mag_opt = MagData(data.a, np.zeros((3,) + data.dim))
     mag_opt.set_vector(x_opt, data.mask)
     return mag_opt
-
-
-def optimize_simple_leastsq(phase_map, mask, b_0=1, lam=1E-4, order=0):
-    '''Reconstruct a magnetic distribution for a 2-D problem with known pixel locations.
-
-    Parameters
-    ----------
-    phase_map : :class:`~pyramid.phasemap.PhaseMap`
-        A :class:`~pyramid.phasemap.PhaseMap` object, representing the phase from which to
-        reconstruct the magnetic distribution.
-    mask : :class:`~numpy.ndarray` (N=3)
-        A boolean matrix (or a matrix consisting of ones and zeros), representing the
-        positions of the magnetized voxels in 3 dimensions.
-    b_0 : float, optional
-        The magnetic induction corresponding to a magnetization `M`\ :sub:`0` in T.
-        The default is 1.
-    lam : float, optional
-        The regularisation parameter. Defaults to 1E-4.
-    order : int {0, 1}, optional
-        order of the regularisation function. Default is 0 for a Tikhonov regularisation of order
-        zero. A first order regularisation, which uses the derivative is available with value 1.
-
-    Returns
-    -------
-    mag_data : :class:`~pyramid.magdata.MagData`
-        The reconstructed magnetic distribution as a :class:`~.MagData` object.
-
-    Notes
-    -----
-    Only works for a single phase_map, if the positions of the magnetized voxels are known and
-    for slice thickness of 1 (constraint for the `z`-dimension).
-
-    '''
-    # Read in parameters:
-    y_m = phase_map.phase_vec  # Measured phase map as a vector
-    a = phase_map.a  # Grid spacing
-    dim = mask.shape  # Dimensions of the mag. distr.
-    count = mask.sum()  # Number of pixels with magnetization
-    # Create empty MagData object for the reconstruction:
-    mag_data_rec = MagData(a, np.zeros((3,) + dim))
-
-    # Function that returns the phase map for a magnetic configuration x:
-    def F(x):
-        mag_data_rec.set_vector(x, mask)
-        proj = SimpleProjector(dim)
-        phase_map = PhaseMapperRDFC(Kernel(a, proj.dim_uv, b_0))(proj(mag_data_rec))
-        return phase_map.phase_vec
-
-    # Cost function of order zero which should be minimized:
-    def J_0(x_i):
-        y_i = F(x_i)
-        term1 = (y_i - y_m)
-        term2 = lam * x_i
-        return np.concatenate([term1, term2])
-
-    # First order cost function which should be minimized:
-    def J_1(x_i):
-        y_i = F(x_i)
-        term1 = (y_i - y_m)
-        mag_data = mag_data_rec.magnitude
-        term2 = []
-        for i in range(3):
-            component = mag_data[i, ...]
-            for j in range(3):
-                if component.shape[j] > 1:
-                    term2.append(np.diff(component, axis=j).reshape(-1))
-
-        term2 = lam * np.concatenate(term2)
-        return np.concatenate([term1, term2])
-
-    J_DICT = [J_0, J_1]  # list of cost-functions with different regularisations
-    # Reconstruct the magnetization components:
-    # TODO Use jutil.minimizer.minimize(jutil.costfunction.LeastSquaresCostFunction(J_DICT[order],
-    # ...) or a simpler frontend.
-    x_rec, _ = leastsq(J_DICT[order], np.zeros(3 * count))
-    mag_data_rec.set_vector(x_rec, mask)
-    return mag_data_rec
diff --git a/pyramid/regularisator.py b/pyramid/regularisator.py
index 5c115bb..5f02f24 100644
--- a/pyramid/regularisator.py
+++ b/pyramid/regularisator.py
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
-"""
-Created on Mon Aug 18 09:24:58 2014
-
-@author: Jan
-"""  # TODO: Docstring!
+# Copyright 2014 by Forschungszentrum Juelich GmbH
+# Author: J. Caron
+#
+"""This module provides the :class:`~.Regularisator` class which represents a regularisation term
+which adds additional constraints to a costfunction to minimize."""
 
 
 import abc
@@ -19,14 +19,23 @@ import logging
 
 __all__ = ['NoneRegularisator', 'ZeroOrderRegularisator', 'FirstOrderRegularisator']
 
-# TODO: Fragen für Jörn: Macht es Sinn, x_a standardmäßig auf den Nullvektor zu setzen? Ansonsten
-#       besser im jeweiligen Konstruktor setzen, nicht im abstrakten!
-#       Wie kommt man genau an die Ableitungen (Normen sind nicht unproblematisch)?
-#       Wie implementiert man am besten verschiedene Normen?
-
 
 class Regularisator(object):
-    # TODO: Docstring!
+    '''Class for providing a regularisation term which implements additional constraints.
+
+    Represents a certain constraint for the 3D magnetization distribution whose cost is to minimize
+    in addition to the derivation from the 2D phase maps. Important is the used `norm` and the
+    regularisation parameter `lam` (lambda) which determines the weighting between the two cost
+    parts (measurements and regularisation).
+
+    Attributes
+    ----------
+    norm: :class:`~jutil.norm.WeightedNorm`
+        Norm, which is used to determine the cost of the regularisation term.
+    lam: float
+        Regularisation parameter determining the weighting between measurements and regularisation.
+
+    '''
 
     __metaclass__ = abc.ABCMeta
     _log = logging.getLogger(__name__+'.Regularisator')
@@ -51,25 +60,71 @@ class Regularisator(object):
         return 'Regularisator(norm=%s, lam=%s)' % (self.norm, self.lam)
 
     def jac(self, x):
-        # TODO: Docstring!
+        '''Calculate the derivative of the regularisation term for a given magnetic distribution.
+
+        Parameters
+        ----------
+        x: :class:`~numpy.ndarray` (N=1)
+            Vectorized magnetization distribution, for which the Jacobi vector is calculated.
+
+        Returns
+        -------
+        result : :class:`~numpy.ndarray` (N=1)
+            Jacobi vector which represents the cost derivative of all voxels of the magnetization.
+
+        '''
         return self.lam * self.norm.jac(x)
 
     def hess_dot(self, x, vector):
-        # TODO: Docstring!
+        '''Calculate the product of a `vector` with the Hessian matrix of the regularisation term.
+
+        Parameters
+        ----------
+        x : :class:`~numpy.ndarray` (N=1)
+            Vectorized magnetization distribution at which the Hessian is calculated. The Hessian
+            is constant in this case, thus `x` can be set to None (it is not used int the
+            computation). It is implemented for the case that in the future nonlinear problems
+            have to be solved.
+        vector : :class:`~numpy.ndarray` (N=1)
+            Vectorized magnetization distribution which is multiplied by the Hessian.
+
+        Returns
+        -------
+        result : :class:`~numpy.ndarray` (N=1)
+            Product of the input `vector` with the Hessian matrix of the costfunction.
+
+        '''
         return self.lam * self.norm.hess_dot(x, vector)
 
     def hess_diag(self, x, vector):
-        # TODO: Docstring!
+        ''' Return the diagonal of the Hessian.
+
+        Parameters
+        ----------
+        _ : undefined
+            Unused input
+
+        '''
         self._log.debug('Calling hess_diag')
         return self.lam * self.norm.hess_diag(x, vector)
 
 
 class NoneRegularisator(Regularisator):
-    # TODO: Docstring
+    '''Placeholder class if no regularization is used.
 
-    # TODO: Necessary class? Use others with lam=0?
+    This class is instantiated in the :class:`~pyramid.costfunction.Costfunction`, which means
+    no regularisation is used. All associated functions return appropriate zero-values.
 
-    LOG = logging.getLogger(__name__+'.NoneRegularisator')
+    Attributes
+    ----------
+    norm: None
+        No regularization is used, thus also no norm.
+    lam: 0
+        Not used.
+
+    '''
+
+    _log = logging.getLogger(__name__+'.NoneRegularisator')
 
     def __init__(self):
         self._log.debug('Calling __init__')
@@ -82,23 +137,72 @@ class NoneRegularisator(Regularisator):
         return 0
 
     def jac(self, x):
-        # TODO: Docstring!
+        '''Calculate the derivative of the regularisation term for a given magnetic distribution.
+
+        Parameters
+        ----------
+        x: :class:`~numpy.ndarray` (N=1)
+            Vectorized magnetization distribution, for which the Jacobi vector is calculated.
+
+        Returns
+        -------
+        result : :class:`~numpy.ndarray` (N=1)
+            Jacobi vector which represents the cost derivative of all voxels of the magnetization.
+
+        '''
         return np.zeros_like(x)
 
     def hess_dot(self, x, vector):
-        # TODO: Docstring!
+        '''Calculate the product of a `vector` with the Hessian matrix of the regularisation term.
+
+        Parameters
+        ----------
+        x : :class:`~numpy.ndarray` (N=1)
+            Vectorized magnetization distribution at which the Hessian is calculated. The Hessian
+            is constant in this case, thus `x` can be set to None (it is not used int the
+            computation). It is implemented for the case that in the future nonlinear problems
+            have to be solved.
+        vector : :class:`~numpy.ndarray` (N=1)
+            Vectorized magnetization distribution which is multiplied by the Hessian.
+
+        Returns
+        -------
+        result : :class:`~numpy.ndarray` (N=1)
+            Product of the input `vector` with the Hessian matrix of the costfunction.
+
+        '''
         return np.zeros_like(vector)
 
     def hess_diag(self, x, vector):
-        # TODO: Docstring!
+        ''' Return the diagonal of the Hessian.
+
+        Parameters
+        ----------
+        _ : undefined
+            Unused input
+
+        '''
         self._log.debug('Calling hess_diag')
         return np.zeros_like(vector)
 
 
 class ZeroOrderRegularisator(Regularisator):
-    # TODO: Docstring!
+    '''Class for providing a regularisation term which implements Lp norm minimization.
+
+    The constraint this class represents is the minimization of the Lp norm for the 3D
+    magnetization distribution. Important is the regularisation parameter `lam` (lambda) which
+    determines the weighting between the two cost parts (measurements and regularisation).
+
+    Attributes
+    ----------
+    lam: float
+        Regularisation parameter determining the weighting between measurements and regularisation.
+    p: int, optional
+        Order of the norm (default: 2, which means a standard L2-norm).
+
+    '''
 
-    LOG = logging.getLogger(__name__+'.ZeroOrderRegularisator')
+    _log = logging.getLogger(__name__+'.ZeroOrderRegularisator')
 
     def __init__(self, _, lam, p=2):
         self._log.debug('Calling __init__')
@@ -112,7 +216,23 @@ class ZeroOrderRegularisator(Regularisator):
 
 
 class FirstOrderRegularisator(Regularisator):
-    # TODO: Docstring!
+    '''Class for providing a regularisation term which implements derivation minimization.
+
+    The constraint this class represents is the minimization of the first order derivative of the
+    3D magnetization distribution using a Lp norm. Important is the regularisation parameter `lam`
+    (lambda) which determines the weighting between the two cost parts (measurements and
+    regularisation).
+
+    Attributes
+    ----------
+    mask: :class:`~numpy.ndarray` (N=3)
+        A boolean mask which defines the magnetized volume in 3D.
+    lam: float
+        Regularisation parameter determining the weighting between measurements and regularisation.
+    p: int, optional
+        Order of the norm (default: 2, which means a standard L2-norm).
+
+    '''
 
     def __init__(self, mask, lam, p=2):
         self.p = p
diff --git a/pyramid/version.py b/pyramid/version.py
index e604916..81491db 100644
--- a/pyramid/version.py
+++ b/pyramid/version.py
@@ -1,3 +1,3 @@
 # THIS FILE IS GENERATED FROM THE PYRAMID SETUP.PY
 version = "0.1.0-dev"
-hg_revision = "9013fd38a1b6+"
+hg_revision = "51a00f1d3934+"
diff --git a/scripts/collaborations/patrick_nanowire_reconstruction.py b/scripts/collaborations/patrick_nanowire_reconstruction.py
index deb5d33..80c625b 100644
--- a/scripts/collaborations/patrick_nanowire_reconstruction.py
+++ b/scripts/collaborations/patrick_nanowire_reconstruction.py
@@ -20,26 +20,29 @@ LOGGING_CONF = os.path.join(os.path.dirname(os.path.realpath(pyramid.__file__)),
 
 logging.config.fileConfig(LOGGING_CONF, disable_existing_loggers=False)
 
+# TODO: read in input via txt-file dictionary?
+
 ###################################################################################################
 a = 1.455  # in nm
 gain = 5
 b_0 = 1
-lam = 1E-6
+lam = 1E-4
 PATH = '../../output/patrick/'
-PHASE = '30_pha'
-MASK = 'mask30_elong_5'
-longFOV = True
+PHASE = 'pos3_40deg_magphase'
+MASK = 'pos3_40deg_maskbyhand'
+FORMAT = '.bmp'
+longFOV = False
 longFOV_string = np.where(longFOV, 'longFOV', 'normalFOV')
 IMAGENAME = '{}_{}_{}_'.format(MASK, PHASE, longFOV_string)
-PHASE_MAX = 7.68  # -10°: 0.92, 30°: 7.68
-PHASE_MIN = -18.89  # -10°: -16.85, 30°: -18.89
+PHASE_MAX = 0.5  # -10°: 0.92, 30°: 7.68
+PHASE_MIN = -0.5  # -10°: -16.85, 30°: -18.89
 PHASE_DELTA = PHASE_MAX - PHASE_MIN
 ###################################################################################################
 
 # Read in files:
-im_mask = Image.open(PATH+MASK+'.tif')
+im_mask = Image.open(PATH+MASK+FORMAT)
 #im_mask.thumbnail((125, 175), Image.ANTIALIAS)
-im_phase = Image.open(PATH+PHASE+'.tif')
+im_phase = Image.open(PATH+PHASE+FORMAT)
 #im_phase.thumbnail((125, 125), Image.ANTIALIAS)
 
 mask = np.array(np.array(im_mask)/255, dtype=bool)
@@ -67,7 +70,7 @@ if longFOV:
 regularisator = FirstOrderRegularisator(mask, lam, p=2)
 
 with TakeTime('reconstruction time'):
-    mag_data_rec = rc.optimize_linear(data_set, regularisator=regularisator, max_iter=50)
+    mag_data_rec = rc.optimize_linear(data_set, regularisator=regularisator, max_iter=1000)[0]
 
 phase_map_rec_pad = pm(mag_data_rec)
 phase_map_rec = PhaseMap(a, phase_map_rec_pad.phase[pad:, :])#[pad:-pad, pad:-pad])
@@ -83,6 +86,6 @@ phase_map_rec.display_combined('Reconstr. Distribution', gain=4)
 plt.savefig(PATH+IMAGENAME+'RECONSTRUCTION.png')
 phase_map_diff.display_combined('Difference')
 plt.savefig(PATH+IMAGENAME+'DIFFERENCE.png')
-mag_data_rec.scale_down(4)
-mag_data_rec.quiver_plot(log=True)
+#mag_data_rec.scale_down(4)
+mag_data_rec.quiver_plot(log=True, ar_dens=8)
 plt.savefig(PATH+IMAGENAME+'MAGNETIZATION_DOWNSCALE4.png')
diff --git a/scripts/collaborations/patrick_nanowire_reconstruction_simulation.py b/scripts/collaborations/patrick_nanowire_reconstruction_simulation.py
index 3c1edba..81efbba 100644
--- a/scripts/collaborations/patrick_nanowire_reconstruction_simulation.py
+++ b/scripts/collaborations/patrick_nanowire_reconstruction_simulation.py
@@ -28,7 +28,7 @@ length = 300
 cutoff = 50
 trans = 40
 pads = [100, 250, 500, 800]
-INPATH = '../../output/vtk data\longtube_withcap/'
+INPATH = '../../output/vtk data/longtube_withcap/'
 OUTPATH = '../../output/patrick/'
 FILE = 'CoFeB_tube_cap_4nm_lying_down'
 ###################################################################################################
diff --git a/scripts/collaborations/rueffner_file.py b/scripts/collaborations/rueffner_file.py
index 230c4cb..82b13fb 100644
--- a/scripts/collaborations/rueffner_file.py
+++ b/scripts/collaborations/rueffner_file.py
@@ -11,6 +11,9 @@ from tqdm import tqdm
 
 import tables
 
+import psutil
+import gc
+
 import pyramid
 from pyramid.magdata import MagData
 from pyramid.projector import SimpleProjector
@@ -25,6 +28,7 @@ LOGGING_CONF = os.path.join(os.path.dirname(os.path.realpath(pyramid.__file__)),
 
 
 logging.config.fileConfig(LOGGING_CONF, disable_existing_loggers=False)
+proc = psutil.Process(os.getpid())
 ###################################################################################################
 PATH = '../../output/'
 dim = (16, 190, 220)
@@ -68,7 +72,7 @@ zs = np.arange(-dim[0]/2, dim[0]/2)
 xx, yy = np.meshgrid(xs, ys)
 
 
-def calculate(t):  # TODO: Somehow avoid memory error :-(...
+def calculate(t):
     print 't =', t
     vectors = h5file.root.data.fields.m.read(field='m_CoFeb')[t, ...]
     data = np.hstack((points, vectors))
@@ -103,3 +107,5 @@ def calculate(t):  # TODO: Somehow avoid memory error :-(...
 # Interpolation and phase calculation for all timesteps:
 for t in np.arange(0, 1001, 5):
     calculate(t)
+    gc.collect()
+    print 'RSS = {:.2f} MB'.format(proc.memory_info().rss/1024.**2)
diff --git a/scripts/collaborations/vtk_conversion.py b/scripts/collaborations/vtk_conversion.py
new file mode 100644
index 0000000..62f21be
--- /dev/null
+++ b/scripts/collaborations/vtk_conversion.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+"""Created on Fri Jan 24 11:17:11 2014 @author: Jan"""
+
+
+import os
+
+import numpy as np
+import matplotlib.pyplot as plt
+from pylab import griddata
+
+import pickle
+import vtk
+from tqdm import tqdm
+
+import pyramid
+from pyramid.magdata import MagData
+from pyramid.projector import XTiltProjector
+from pyramid.phasemapper import PhaseMapperRDFC
+from pyramid.kernel import Kernel
+
+import logging
+import logging.config
+
+
+LOGGING_CONF = os.path.join(os.path.dirname(os.path.realpath(pyramid.__file__)), 'logging.ini')
+
+
+logging.config.fileConfig(LOGGING_CONF, disable_existing_loggers=False)
+###################################################################################################
+PATH = '../../output/vtk data/tube_160x30x1100nm/02758'
+b_0 = 1.54
+gain = 8
+force_calculation = False
+###################################################################################################
+# Load vtk-data:
+if force_calculation or not os.path.exists(PATH+'.pickle'):
+    # Setting up reader:
+    reader = vtk.vtkDataSetReader()
+    reader.SetFileName(PATH+'.vtk')
+    reader.ReadAllScalarsOn()
+    reader.ReadAllVectorsOn()
+    reader.Update()
+    # Getting output:
+    output = reader.GetOutput()
+    # Reading points and vectors:
+    size = output.GetNumberOfPoints()
+    vtk_points = output.GetPoints().GetData()
+    vtk_vectors = output.GetPointData().GetVectors()
+    # Converting points to numpy array:
+    point_array = np.zeros(vtk_points.GetSize())
+    vtk_points.ExportToVoidPointer(point_array)
+    point_array = np.reshape(point_array, (-1, 3))
+    # Converting vectors to numpy array:
+    vector_array = np.zeros(vtk_points.GetSize())
+    vtk_vectors.ExportToVoidPointer(vector_array)
+    vector_array = np.reshape(vector_array, (-1, 3))
+    # Combining data:
+    data = np.hstack((point_array, vector_array))
+    with open(PATH+'.pickle', 'w') as pf:
+        pickle.dump(data, pf)
+else:
+    with open(PATH+'.pickle') as pf:
+        data = pickle.load(pf)
+# Scatter plot of all x-y-coordinates
+axis = plt.figure().add_subplot(1, 1, 1)
+axis.scatter(data[:, 0], data[:, 1])
+plt.show()
+###################################################################################################
+# Interpolate on regular grid:
+if force_calculation or not os.path.exists(PATH+'.nc'):
+    # Find unique z-slices:
+    zs = np.unique(data[:, 2])
+    # Determine the grid spacing:
+    a = zs[1] - zs[0]
+    # Determine the size of object:
+    x_min, x_max = data[:, 0].min(), data[:, 0].max()
+    y_min, y_max = data[:, 1].min(), data[:, 1].max()
+    z_min, z_max = data[:, 2].min(), data[:, 2].max()
+    x_diff, y_diff, z_diff = np.ptp(data[:, 0]), np.ptp(data[:, 1]), np.ptp(data[:, 2])
+    x_cent, y_cent, z_cent = x_min+x_diff/2., y_min+y_diff/2., z_min+z_diff/2.
+    # Create regular grid
+    xs = np.arange(x_cent-x_diff, x_cent+x_diff, a)
+    ys = np.arange(y_cent-y_diff, y_cent+y_diff, a)
+    o, p = np.meshgrid(xs, ys)
+    # Create empty magnitude:
+    magnitude = np.zeros((3, len(zs), len(ys), len(xs)))
+
+    # WITH MASKING OF THE CENTER (SYMMETRIC):
+    iz_x = np.concatenate([np.linspace(-4.95, -4.95, 50),
+                           np.linspace(-4.95, 0, 50),
+                           np.linspace(0, 4.95, 50),
+                           np.linspace(4.95, 4.95, 50),
+                           np.linspace(-4.95, 0, 50),
+                           np.linspace(0, 4.95, 50), ])
+    iz_y = np.concatenate([np.linspace(-2.88, 2.88, 50),
+                           np.linspace(2.88, 5.7, 50),
+                           np.linspace(5.7, 2.88, 50),
+                           np.linspace(2.88, -2.88, 50),
+                           np.linspace(-2.88, -5.7, 50),
+                           np.linspace(-5.7, -2.88, 50), ])
+    for i, z in tqdm(enumerate(zs), total=len(zs)):
+        subdata = data[data[:, 2] == z, :]
+        for j in range(3):  # For all 3 components!
+            gridded_subdata = griddata(np.concatenate([subdata[:, 0], iz_x]),
+                                       np.concatenate([subdata[:, 1], iz_y]),
+                                       np.concatenate([subdata[:, 3 + j], np.zeros(len(iz_x))]),
+                                       o, p)
+            magnitude[j, i, :, :] = gridded_subdata.filled(fill_value=0)
+
+#    # WITH MASKING OF THE CENTER (ASYMMETRIC):
+#    iz_x = np.concatenate([np.linspace(-5.88, -5.88, 50),
+#                           np.linspace(-5.88, 0, 50),
+#                            np.linspace(0, 5.88, 50),
+#                            np.linspace(5.88, 5.88, 50),
+#                            np.linspace(5.88, 0, 50),
+#                            np.linspace(0, -5.88, 50),])
+#    iz_y = np.concatenate([np.linspace(-2.10, 4.50, 50),
+#                           np.linspace(4.50, 7.90, 50),
+#                            np.linspace(7.90, 4.50, 50),
+#                            np.linspace(4.50, -2.10, 50),
+#                            np.linspace(-2.10, -5.50, 50),
+#                            np.linspace(-5.50, -2.10, 50), ])
+#    for i, z in tqdm(enumerate(zs), total=len(zs)):
+#        subdata = data[data[:, 2] == z, :]
+#        for j in range(3):  # For all 3 components!
+#            gridded_subdata = griddata(np.concatenate([subdata[:, 0], iz_x]),
+#                                       np.concatenate([subdata[:, 1], iz_y]),
+#                                       np.concatenate([subdata[:, 3 + j], np.zeros(len(iz_x))]),
+#                                       o, p)
+#            magnitude[j, i, :, :] = gridded_subdata.filled(fill_value=0)
+
+#    # WITHOUT MASKING OF THE CENTER:
+#    for i, z in tqdm(enumerate(zs), total=len(zs)):
+#        subdata = data[data[:, 2] == z, :]
+#        for j in range(3):  # For all 3 components!
+#            gridded_subdata = griddata(subdata[:, 0], subdata[:, 1], subdata[:, 3 + j], o, p)
+#            magnitude[j, i, :, :] = gridded_subdata.filled(fill_value=0)
+
+    # Creating MagData object:
+    mag_data = MagData(0.2*10, magnitude)
+    mag_data.save_to_netcdf4(PATH+'.nc')
+else:
+    mag_data = MagData.load_from_netcdf4(PATH+'.nc')
+mag_data.quiver_plot()
+###################################################################################################
+# Turn magnetization around by 90° around x-axis:
+#magnitude_new = np.zeros((3, mag_data.dim[1], mag_data.dim[0], mag_data.dim[2]))
+#for i in range(mag_data.dim[2]):
+#    x_rot = np.rot90(mag_data.magnitude[0, ..., i]).copy()
+#    y_rot = np.rot90(mag_data.magnitude[1, ..., i]).copy()
+#    z_rot = np.rot90(mag_data.magnitude[2, ..., i]).copy()
+#    magnitude_new[0, ..., i] = x_rot
+#    magnitude_new[1, ..., i] = z_rot
+#    magnitude_new[2, ..., i] = -y_rot
+#mag_data.magnitude = magnitude_new
+#mag_data.save_to_netcdf4(PATH+'_lying_down.nc')
+
+
+dim = mag_data.dim
+dim_uv = (500, 200)
+angles = [0, 20, 40, 60]
+
+mag_data_xy = mag_data.copy()
+mag_data_xy.magnitude[2] = 0
+
+mag_data_z = mag_data.copy()
+mag_data_z.magnitude[0] = 0
+mag_data_z.magnitude[1] = 0
+
+# Iterate over all angles:
+for angle in angles:
+    angle_rad = np.pi/2 + angle*np.pi/180
+    projector = XTiltProjector(dim, angle_rad, dim_uv)
+    mag_proj = projector(mag_data_z)
+    phase_map = PhaseMapperRDFC(Kernel(mag_data.a, projector.dim_uv))(mag_proj)
+    phase_map.display_combined('Phase Map Nanowire Tip', gain=gain,
+                               interpolation='bilinear')
+    plt.savefig(PATH+'_nanowire_z_xtilt_{}.png'.format(angle), dpi=500)
+    mag_proj.scale_down(2)
+    axis = mag_proj.quiver_plot()
+    plt.savefig(PATH+'_nanowire_z_mag_xtilt_{}.png'.format(angle), dpi=500)
+    axis = mag_proj.quiver_plot(log=True)
+    plt.savefig(PATH+'_nanowire_z_mag_log_xtilt_{}.png'.format(angle), dpi=500)
+    # Close plots:
+    plt.close('all')
+
+
+#mag_data.scale_down(2)
+#mag_data.quiver_plot3d()
+#
+#mag_data_xy.scale_down(2)
+#mag_data_xy.quiver_plot3d()
+#
+#mag_data_z.scale_down(2)
+#mag_data_z.quiver_plot3d()
diff --git a/scripts/collaborations/vtk_conversion_nanowire_full_90deg.py b/scripts/collaborations/vtk_conversion_nanowire_full_90deg.py
index d1ab275..2f01c98 100644
--- a/scripts/collaborations/vtk_conversion_nanowire_full_90deg.py
+++ b/scripts/collaborations/vtk_conversion_nanowire_full_90deg.py
@@ -128,13 +128,13 @@ dim_uv = (600, 150)
 angles = [0, 10, 20, 30, 40, 50, 60]
 # Turn magnetization around by 90° around x-axis:
 magnitude_new = np.zeros((3, mag_data.dim[1], mag_data.dim[0], mag_data.dim[2]))
-for i in range(mag_data.magnitude.shape[2]):
+for i in range(mag_data.dim[2]):
     x_rot = np.rot90(mag_data.magnitude[0, ..., i]).copy()
     y_rot = np.rot90(mag_data.magnitude[1, ..., i]).copy()
     z_rot = np.rot90(mag_data.magnitude[2, ..., i]).copy()
     magnitude_new[0, ..., i] = x_rot
-    magnitude_new[1, ..., i] = -z_rot
-    magnitude_new[2, ..., i] = y_rot
+    magnitude_new[1, ..., i] = z_rot
+    magnitude_new[2, ..., i] = -y_rot
 mag_data.magnitude = magnitude_new
 mag_data.save_to_netcdf4(PATH+'_lying_down.nc')
 # Iterate over all angles:
diff --git a/scripts/reconstruction/reconstruct_random_pixels.py b/scripts/reconstruction/reconstruct_random_pixels.py
index 3aec76e..7a12148 100644
--- a/scripts/reconstruction/reconstruct_random_pixels.py
+++ b/scripts/reconstruction/reconstruct_random_pixels.py
@@ -13,6 +13,8 @@ import pyramid
 import pyramid.magcreator as mc
 from pyramid.magdata import MagData
 from pyramid.phasemapper import pm
+from pyramid.dataset import DataSet
+from pyramid.projector import SimpleProjector
 import pyramid.reconstruction as rc
 
 import logging
@@ -45,7 +47,10 @@ phase_map = pm(mag_data)
 phase_map.display_combined('Generated Distribution', gain=10)
 
 # Reconstruct the magnetic distribution:
-mag_data_rec = rc.optimize_simple_leastsq(phase_map, mag_data.get_mask(), b_0, lam=1E-4, order=1)
+
+data = DataSet(a, dim, b_0, mag_data.get_mask())
+data.append(phase_map, SimpleProjector(dim))
+mag_data_rec, cost = rc.optimize_linear(data, max_iter=100)
 
 # Display the reconstructed phase map and holography image:
 phase_map_rec = pm(mag_data_rec)
diff --git a/scripts/reconstruction/reconstruction_linear_test.py b/scripts/reconstruction/reconstruction_linear_test.py
index 7da8b7a..264625a 100644
--- a/scripts/reconstruction/reconstruction_linear_test.py
+++ b/scripts/reconstruction/reconstruction_linear_test.py
@@ -36,7 +36,6 @@ dim = (32, 32, 32)
 dim_uv = dim[1:3]
 count = 16
 lam = 1E-4
-use_fftw = True
 center = (dim[0]/2-0.5, int(dim[1]/2)-0.5, int(dim[2]/2)-0.5)
 radius_core = dim[1]/8
 radius_shell = dim[1]/4
@@ -52,10 +51,6 @@ shape = mc.Shapes.disc(dim, center, radius_shell, height)
 magnitude = mc.create_mag_dist_vortex(shape)
 mag_data = MagData(a, magnitude)
 
-mag_data.quiver_plot('z-projection', proj_axis='z')
-mag_data.quiver_plot('y-projection', proj_axis='y')
-mag_data.quiver_plot3d('Original distribution')
-
 tilts_full = np.linspace(-pi/2, pi/2, num=count/2, endpoint=False)
 tilts_miss = np.linspace(-pi/3, pi/3, num=count/2, endpoint=False)
 
@@ -77,7 +72,7 @@ noise = 0
 print('--Setting up data collection')
 
 mask = mag_data.get_mask()
-data = DataSet(a, dim, b_0, mask, use_fftw=use_fftw)
+data = DataSet(a, dim, b_0, mask)
 data.projectors = projectors
 data.phase_maps = data.create_phase_maps(mag_data)
 
@@ -97,26 +92,43 @@ print 'Cost:', cost.chisq
 ###################################################################################################
 print('--Plot stuff')
 
-mag_data_opt.quiver_plot3d('Reconstructed distribution')
+limit = 1.2
+mag_data.quiver_plot3d('Original distribution', limit=limit)
+mag_data_opt.quiver_plot3d('Reconstructed distribution', limit=limit)
 (mag_data_opt - mag_data).quiver_plot3d('Difference')
 phase_maps_opt = data.create_phase_maps(mag_data_opt)
 
-# TODO: iterations in jutil is one to many!
 
 from pyramid.diagnostics import Diagnostics
+from matplotlib.patches import Rectangle
 
-diag = Diagnostics(mag_data_opt.mag_vec, cost)
-
-print 'std:', diag.std
-gain_maps = diag.get_gain_maps()
-gain_maps[count//2].display_phase()
-diag.get_avg_kernel().quiver_plot3d()
-measure_contribution = diag.measure_contribution
 
-diag.set_position(cost.data_set.mask.sum()//2)
+diag = Diagnostics(mag_data_opt.mag_vec, cost, max_iter=2000)
 
+print 'position:', diag.pos
+print 'std:', diag.std
+gain_maps = diag.get_gain_row_maps()
+axis, cbar = gain_maps[count//2].display_phase()
+axis.add_patch(Rectangle((diag.pos[3], diag.pos[2]), 1, 1, linewidth=2, color='g', fill=False))
+cbar.set_label(u'magnetization/phase [1/rad]', fontsize=15)
+diag.get_avg_kern_row().quiver_plot3d()
+mcon = diag.measure_contribution
+print 'measurement contr. (min - max): {:.2f} - {:.2f}'.format(mcon.min(), mcon.max())
+px_avrg, fwhm, (x, y, z) = diag.calculate_averaging()
+print 'px_avrg:', px_avrg
+print 'fwhm:', fwhm
+
+diag.pos = (1, dim[0]//2, dim[1]//2, dim[2]//2)
+
+print 'position:', diag.pos
 print 'std:', diag.std
-gain_maps = diag.get_gain_maps()
-gain_maps[count//2].display_phase()
-diag.get_avg_kernel().quiver_plot3d()
-measure_contribution = diag.measure_contribution
+gain_maps = diag.get_gain_row_maps()
+axis, cbar = gain_maps[count//2].display_phase()
+axis.add_patch(Rectangle((diag.pos[3], diag.pos[2]), 1, 1, linewidth=2, color='g', fill=False))
+cbar.set_label(u'magnetization/phase [1/rad]', fontsize=15)
+diag.get_avg_kern_row().quiver_plot3d()
+mcon = diag.measure_contribution
+print 'measurement contr. (min - max): {:.2f} - {:.2f}'.format(mcon.min(), mcon.max())
+px_avrg, fwhm, (x, y, z) = diag.calculate_averaging()
+print 'px_avrg:', px_avrg
+print 'fwhm:', fwhm
diff --git a/scripts/reconstruction/reconstruction_linear_test_batch.py b/scripts/reconstruction/reconstruction_linear_test_batch.py
index 3f310a8..2de7ce4 100644
--- a/scripts/reconstruction/reconstruction_linear_test_batch.py
+++ b/scripts/reconstruction/reconstruction_linear_test_batch.py
@@ -108,7 +108,7 @@ for i, configuration in enumerate(itertools.product(*config_list)):
     # DataSet:
     data = DataSet(a, dim, b_0, mask)
     # Tilts:
-    tilts = np.arange(-max_tilt/180.*pi, max_tilt/180.*pi, tilt_step/180.*pi)
+    tilts = np.deg2rad(np.arange(-max_tilt, max_tilt, tilt_step))
     # Projectors:
     projectors = []
     if xy_tilts[xy_key][0]:
diff --git a/scripts/reconstruction/reconstruction_sparse_cg_singular_values.py b/scripts/reconstruction/reconstruction_sparse_cg_singular_values.py
index 0ccee36..df39915 100644
--- a/scripts/reconstruction/reconstruction_sparse_cg_singular_values.py
+++ b/scripts/reconstruction/reconstruction_sparse_cg_singular_values.py
@@ -64,7 +64,7 @@ F = ForwardModel(data)
 M = np.asmatrix([F.jac_dot(None, np.eye(3*size_3d)[:, i]) for i in range(3*size_3d)]).T
 # MTM = M.T * M + lam * np.asmatrix(np.eye(3*size_3d))
 
-U, s, V = np.linalg.svd(M)  # TODO: M or MTM?
+U, s, V = np.linalg.svd(M)  # TODO: M or MTM
 
 for i in range(len(s)):
     print 'Singular value:', s[i]
diff --git a/scripts/test methods/histo_norm.py b/scripts/test methods/histo_norm.py
new file mode 100644
index 0000000..7a9dea3
--- /dev/null
+++ b/scripts/test methods/histo_norm.py	
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Thu Jan 22 14:30:14 2015
+
+@author: Jan
+"""
+
+
+import matplotlib.pyplot as plt
+
+from scipy import misc
+import scipy as sp
+
+
+im = misc.lena()
+
+plt.figure()
+plt.imshow(im, cmap=plt.cm.gray)
+plt.show()
+
+
+def histeq(im, nbr_bins=256):
+
+    # get image histogram
+    imhist, bins = sp.histogram(im.flatten(), nbr_bins, normed=True)
+    cdf = imhist.cumsum()  # cumulative distribution function
+    cdf = 255 * cdf / cdf[-1]  # normalize
+
+    # use linear interpolation of cdf to find new pixel values
+    im2 = sp.interp(im.flatten(), bins[:-1], cdf)
+
+    return im2.reshape(im.shape), cdf
+
+
+im2, cdf = histeq(im)
+
+plt.figure()
+plt.imshow(im2, cmap=plt.cm.gray)
+plt.show()
diff --git a/tests/test_analytic.py b/tests/test_analytic.py
index 562ee62..dbdd9d6 100644
--- a/tests/test_analytic.py
+++ b/tests/test_analytic.py
@@ -4,9 +4,9 @@
 
 import os
 import unittest
+
 import numpy as np
 from numpy import pi
-
 from numpy.testing import assert_allclose
 
 import pyramid.analytic as an
diff --git a/tests/test_compliance.py b/tests/test_compliance.py
index 6ffe674..b92af3f 100644
--- a/tests/test_compliance.py
+++ b/tests/test_compliance.py
@@ -22,8 +22,8 @@ class TestCaseCompliance(unittest.TestCase):
         for root, dirs, files in os.walk(rootdir):
             for filename in files:
                 if ((filename.endswith('.py') or filename.endswith('.pyx'))
-                    and root != os.path.join(self.path, 'scripts', 'gui')):
-                        filepaths.append(os.path.join(root, filename))
+                        and root != os.path.join(self.path, 'scripts', 'gui')):
+                            filepaths.append(os.path.join(root, filename))
         return filepaths
 
     def test_pep8(self):
diff --git a/tests/test_kernel.py b/tests/test_kernel.py
index 39cb1e1..cfc0bbd 100644
--- a/tests/test_kernel.py
+++ b/tests/test_kernel.py
@@ -1,2 +1,41 @@
 # -*- coding: utf-8 -*-
-"""Created on Mon Jan 20 20:00:50 2014 @author: Jan"""
+"""Testcase for the magdata module."""
+
+
+import os
+import unittest
+
+import numpy as np
+from numpy.testing import assert_allclose
+
+from pyramid.kernel import Kernel
+
+
+class TestCaseKernel(unittest.TestCase):
+
+    def setUp(self):
+        self.path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_kernel')
+        self.kernel = Kernel(1., dim_uv=(4, 4), b_0=1., geometry='disc')
+
+    def tearDown(self):
+        self.path = None
+        self.kernel = None
+
+    def test_kernel(self):
+        np.save(os.path.join(self.path, 'ref_u'), self.kernel.u)
+        np.save(os.path.join(self.path, 'ref_v'), self.kernel.v)
+        np.save(os.path.join(self.path, 'ref_u_fft'), self.kernel.u_fft)
+        np.save(os.path.join(self.path, 'ref_v_fft'), self.kernel.v_fft)
+        ref_u = np.load(os.path.join(self.path, 'ref_u.npy'))
+        ref_v = np.load(os.path.join(self.path, 'ref_v.npy'))
+        ref_u_fft = np.load(os.path.join(self.path, 'ref_u_fft.npy'))
+        ref_v_fft = np.load(os.path.join(self.path, 'ref_v_fft.npy'))
+        assert_allclose(self.kernel.u, ref_u, err_msg='Unexpected behavior in u')
+        assert_allclose(self.kernel.v, ref_v, err_msg='Unexpected behavior in v')
+        assert_allclose(self.kernel.u_fft, ref_u_fft, err_msg='Unexpected behavior in u_fft')
+        assert_allclose(self.kernel.v_fft, ref_v_fft, err_msg='Unexpected behavior in v_fft')
+
+
+if __name__ == '__main__':
+    suite = unittest.TestLoader().loadTestsFromTestCase(TestCaseKernel)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/tests/test_kernel/ref_u.npy b/tests/test_kernel/ref_u.npy
new file mode 100644
index 0000000000000000000000000000000000000000..794faa4622119f38f46bdf2acc9fad231f56ec4e
GIT binary patch
literal 276
zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh=
zXCxM+0{I%|Itu2RItsN4aKN>mU3140uB;s`3Nv=BXI}}XL41(71iQ?Rw(i;;tk+Hf
z>Az6A7Ay`F+s=B;3oH&)vlAlk1yZw}0S^Gmn*;S(0M!F=ttE&C@j>E1`#@qKHdr1)
Z*IIzYf$p&Yy2}FSP9S?Fm<I7d;s8vcV9fvk

literal 0
HcmV?d00001

diff --git a/tests/test_kernel/ref_u_fft.npy b/tests/test_kernel/ref_u_fft.npy
new file mode 100644
index 0000000000000000000000000000000000000000..30a4c4d9f3fc0560212f7bb98b18930ecd932926
GIT binary patch
literal 400
zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1ZlWd``qoAIaUsO_*m=~X4l#&V(cT3DEP6dh=
zXCxM+0{I#iItr$mItsN4aKObtB$&43`7R)~$_t)s1=1pH_kITmgV@tU`Yf+X?AQ*(
z<~&??EkR;&O1pQ0Fo^wc;SNiwCFd=Gct_eoZLrv`ORIK*Fi8H8^?Xa9Sr#DkIOa2f
z#h%=YumE9@JTU-jo+ZdEka}eI0nN08nzw^k_d)%;3uG3^&oK7^{R(y;)W6#Sc~x}<

literal 0
HcmV?d00001

diff --git a/tests/test_kernel/ref_v.npy b/tests/test_kernel/ref_v.npy
new file mode 100644
index 0000000000000000000000000000000000000000..c854a27d8debf3f664bb65695088dba2ecb41630
GIT binary patch
literal 276
zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1ZlV+l>qoAIaUsO_*m=~X4l#&V(cT3DEP6dh=
zXCxM+0{I%|Itu2RItsN4aKN>mU2}&7yUY&OYp1p|00EFT2l6d|;ucG|vUap}*8=6d
zc0lC3EP#BFxJ8S?43Hk6{9mvfkhTEwLE=EYE5YJGwL3xjfV3q@9w?4iA3po=xf1|X
CgJ8`7

literal 0
HcmV?d00001

diff --git a/tests/test_kernel/ref_v_fft.npy b/tests/test_kernel/ref_v_fft.npy
new file mode 100644
index 0000000000000000000000000000000000000000..d7588d2717005ee4e12782e7b8d6e8873a0aa17b
GIT binary patch
literal 400
zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1ZlWd``qoAIaUsO_*m=~X4l#&V(cT3DEP6dh=
zXCxM+0{I#iItr$mItsN4aKOa?1=E&1-vz{0TwHf8K|*^jt=b8~FnJiAA3WI##Ew(i
zy%U5%?8Dac!FoV!7!a|0zXOCp{C^8~SPI#_w*X==AEpLI&j{(Wlv;A$0*H5T%xA)|
z6Qpi|#E$JiY@V`E8!Z3iUW5e*!_0uuK(oN|Q1ig@*vtgF%>sl$`hf1+3340AjWBbN
OX`r8XLH%oq?pFZnL|hsG

literal 0
HcmV?d00001

diff --git a/tests/test_magcreator.py b/tests/test_magcreator.py
index 137b6e6..4abcb95 100644
--- a/tests/test_magcreator.py
+++ b/tests/test_magcreator.py
@@ -31,11 +31,27 @@ class TestCaseMagCreator(unittest.TestCase):
         assert_allclose(test_disc_x, np.load(os.path.join(self.path, 'ref_disc_x.npy')),
                         err_msg='Created disc in x-direction does not match expectation!')
 
+    def test_shape_ellipse(self):
+        test_ellipse_z = mc.Shapes.ellipse((7, 8, 9), (3, 4, 5), (3, 5), 1, 'z')
+        test_ellipse_y = mc.Shapes.ellipse((7, 8, 9), (3, 4, 5), (3, 5), 1, 'y')
+        test_ellipse_x = mc.Shapes.ellipse((7, 8, 9), (3, 4, 5), (3, 5), 1, 'x')
+        assert_allclose(test_ellipse_z, np.load(os.path.join(self.path, 'ref_ellipse_z.npy')),
+                        err_msg='Created ellipse does not match expectation!')
+        assert_allclose(test_ellipse_y, np.load(os.path.join(self.path, 'ref_ellipse_y.npy')),
+                        err_msg='Created ellipse does not match expectation!')
+        assert_allclose(test_ellipse_x, np.load(os.path.join(self.path, 'ref_ellipse_x.npy')),
+                        err_msg='Created ellipse does not match expectation!')
+
     def test_shape_sphere(self):
         test_sphere = mc.Shapes.sphere((5, 6, 7), (2, 3, 4), 2)
         assert_allclose(test_sphere, np.load(os.path.join(self.path, 'ref_sphere.npy')),
                         err_msg='Created sphere does not match expectation!')
 
+    def test_shape_ellipsoid(self):
+        test_ellipsoid = mc.Shapes.ellipsoid((7, 8, 9), (3, 4, 4), (3, 5, 7))
+        assert_allclose(test_ellipsoid, np.load(os.path.join(self.path, 'ref_ellipsoid.npy')),
+                        err_msg='Created ellipsoid does not match expectation!')
+
     def test_shape_filament(self):
         test_filament_z = mc.Shapes.filament((5, 6, 7), (2, 3), 'z')
         test_filament_y = mc.Shapes.filament((5, 6, 7), (2, 3), 'y')
diff --git a/tests/test_magcreator/ref_ellipse_x.npy b/tests/test_magcreator/ref_ellipse_x.npy
new file mode 100644
index 0000000000000000000000000000000000000000..382ada72ce030957e85c6131100432acada9278b
GIT binary patch
literal 584
zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlVqr_qoAIaUsO_*m=~X4l#&V(cT3DEP6dh=
zXCxM+0{I%|Itms#3YMBW3bhIlz{N1c03*!3m<*T<J{nUO9ua&7!Q?2^4l{=CGyr4A
B6|VpQ

literal 0
HcmV?d00001

diff --git a/tests/test_magcreator/ref_ellipse_y.npy b/tests/test_magcreator/ref_ellipse_y.npy
new file mode 100644
index 0000000000000000000000000000000000000000..c83d0cb376130c7938da3536ba087e3289877170
GIT binary patch
literal 584
zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlVqr_qoAIaUsO_*m=~X4l#&V(cT3DEP6dh=
tXCxM+0{I%|Itms#3YMBW3bhIlz{M~`0V5+LHCzrfhH3^;!##8}0{~;k6|VpQ

literal 0
HcmV?d00001

diff --git a/tests/test_magcreator/ref_ellipse_z.npy b/tests/test_magcreator/ref_ellipse_z.npy
new file mode 100644
index 0000000000000000000000000000000000000000..75439a6e20aa9a53158aec3a1b5c2602ffc979ab
GIT binary patch
literal 584
zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlVqr_qoAIaUsO_*m=~X4l#&V(cT3DEP6dh=
pXCxM+0{I%|Itms#3YMBW3bhIlz{M~!0V5+L*yBJN!5T?H0RUsi6|VpQ

literal 0
HcmV?d00001

diff --git a/tests/test_magcreator/ref_ellipsoid.npy b/tests/test_magcreator/ref_ellipsoid.npy
new file mode 100644
index 0000000000000000000000000000000000000000..8abca7c48658e9f9df018d7869ba2dc4461b1d9a
GIT binary patch
literal 584
zcmbR27wQ`j$;jZwP_3SlTAW;@Zl$1JlVqr_qoAIaUsO_*m=~X4l#&V(cT3DEP6dh=
zXCxM+0{I%|Itms#3YMBW3bhIlz{N0>03!&19E`~z#v4#wU<x~dWHJc`BI$xVj2P_$
GsRjU=5*6nF

literal 0
HcmV?d00001

diff --git a/tests/test_magdata.py b/tests/test_magdata.py
index 14d9660..a178de3 100644
--- a/tests/test_magdata.py
+++ b/tests/test_magdata.py
@@ -4,7 +4,9 @@
 
 import os
 import unittest
+
 import numpy as np
+from numpy.testing import assert_allclose
 
 from pyramid.magdata import MagData
 
@@ -13,77 +15,80 @@ class TestCaseMagData(unittest.TestCase):
 
     def setUp(self):
         self.path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_magdata')
-        magnitude = (np.zeros((4, 4, 4)), np.zeros((4, 4, 4)), np.zeros((4, 4, 4)))
-        magnitude[0][1:-1, 1:-1, 1:-1] = 1
-        magnitude[1][1:-1, 1:-1, 1:-1] = 1
-        magnitude[2][1:-1, 1:-1, 1:-1] = 1
+        magnitude = np.zeros((3, 4, 4, 4))
+        magnitude[:, 1:-1, 1:-1, 1:-1] = 1
         self.mag_data = MagData(10.0, magnitude)
 
     def tearDown(self):
         self.path = None
         self.mag_data = None
 
-    def test_add_magnitude(self):
-        reference = (np.ones((4, 4, 4)), np.ones((4, 4, 4)), np.ones((4, 4, 4)))
-        self.mag_data.add_magnitude(reference)
-        reference[0][1:-1, 1:-1, 1:-1] = 2
-        reference[1][1:-1, 1:-1, 1:-1] = 2
-        reference[2][1:-1, 1:-1, 1:-1] = 2
-        np.testing.assert_equal(self.mag_data.magnitude, reference,
-                                'Unexpected behavior in add_magnitude()!')
+    def test_copy(self):
+        mag_data = self.mag_data
+        mag_data_copy = self.mag_data.copy()
+        assert mag_data == self.mag_data, 'Unexpected behaviour in copy()!'
+        assert mag_data_copy != self.mag_data, 'Unexpected behaviour in copy()!'
+
+    def test_scale_down(self):
+        self.mag_data.scale_down()
+        reference = 1/8. * np.ones((3, 2, 2, 2))
+        assert_allclose(self.mag_data.magnitude, reference,
+                        err_msg='Unexpected behavior in scale_down()!')
+        assert_allclose(self.mag_data.a, 20,
+                        err_msg='Unexpected behavior in scale_down()!')
+
+    def test_scale_up(self):
+        self.mag_data.scale_up()
+        reference = np.zeros((3, 8, 8, 8))
+        reference[:, 2:6, 2:6, 2:6] = 1
+        assert_allclose(self.mag_data.magnitude, reference,
+                        err_msg='Unexpected behavior in scale_down()!')
+        assert_allclose(self.mag_data.a, 5,
+                        err_msg='Unexpected behavior in scale_down()!')
+
+    def test_pad(self):
+        reference = self.mag_data.magnitude.copy()
+        self.mag_data.pad(1, 1, 1)
+        reference = np.pad(reference, ((0, 0), (1, 1), (1, 1), (1, 1)), mode='constant')
+        assert_allclose(self.mag_data.magnitude, reference,
+                        err_msg='Unexpected behavior in scale_down()!')
 
     def test_get_mask(self):
         mask = self.mag_data.get_mask()
         reference = np.zeros((4, 4, 4))
         reference[1:-1, 1:-1, 1:-1] = True
-        np.testing.assert_equal(mask, reference, 'Unexpected behavior in get_mask()!')
+        assert_allclose(mask, reference,
+                        err_msg='Unexpected behavior in get_mask()!')
 
     def test_get_vector(self):
         mask = self.mag_data.get_mask()
         vector = self.mag_data.get_vector(mask)
-        reference = np.ones(np.count_nonzero(self.mag_data.magnitude[0])*3)
-        np.testing.assert_equal(vector, reference, 'Unexpected behavior in get_mask()!')
+        reference = np.ones(np.sum(mask)*3)
+        assert_allclose(vector, reference,
+                        err_msg='Unexpected behavior in get_vector()!')
 
     def test_set_vector(self):
         mask = self.mag_data.get_mask()
-        vector = 2 * np.ones(np.count_nonzero(self.mag_data.magnitude[0])*3)
-        self.mag_data.set_vector(mask, vector)
-        reference = (np.zeros((4, 4, 4)), np.zeros((4, 4, 4)), np.zeros((4, 4, 4)))
-        reference[0][1:-1, 1:-1, 1:-1] = 2
-        reference[1][1:-1, 1:-1, 1:-1] = 2
-        reference[2][1:-1, 1:-1, 1:-1] = 2
-        np.testing.assert_equal(self.mag_data.magnitude, reference,
-                                'Unexpected behavior in set_mask()!')
-
-    def test_scale_down(self):
-        self.mag_data.scale_down()
-        reference = (1/8. * np.ones((2, 2, 2)),
-                     1/8. * np.ones((2, 2, 2)),
-                     1/8. * np.ones((2, 2, 2)))
-        np.testing.assert_equal(self.mag_data.magnitude, reference,
-                                'Unexpected behavior in scale_down()!')
-        np.testing.assert_equal(self.mag_data.res, 20, 'Unexpected behavior in scale_down()!')
+        vector = 2 * np.ones(np.sum(mask)*3)
+        self.mag_data.set_vector(vector, mask)
+        reference = np.zeros((3, 4, 4, 4))
+        reference[:, 1:-1, 1:-1, 1:-1] = 2
+        assert_allclose(self.mag_data.magnitude, reference,
+                        err_msg='Unexpected behavior in set_vector()!')
 
     def test_load_from_llg(self):
-        self.mag_data = MagData.load_from_llg(os.path.join(self.path, 'ref_mag_data.txt'))
-        reference = (np.zeros((4, 4, 4)), np.zeros((4, 4, 4)), np.zeros((4, 4, 4)))
-        reference[0][1:-1, 1:-1, 1:-1] = 1
-        reference[1][1:-1, 1:-1, 1:-1] = 1
-        reference[2][1:-1, 1:-1, 1:-1] = 1
-        np.testing.assert_equal(self.mag_data.magnitude, reference,
-                                'Unexpected behavior in load_from_llg()!')
-        np.testing.assert_equal(self.mag_data.res, 10, 'Unexpected behavior in load_from_llg()!')
+        mag_data = MagData.load_from_llg(os.path.join(self.path, 'ref_mag_data.txt'))
+        assert_allclose(mag_data.magnitude, self.mag_data.magnitude,
+                        err_msg='Unexpected behavior in load_from_llg()!')
+        assert_allclose(mag_data.a, self.mag_data.a,
+                        err_msg='Unexpected behavior in load_from_llg()!')
 
     def test_load_from_netcdf4(self):
-        self.mag_data = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_data.nc'))
-        reference = (np.zeros((4, 4, 4)), np.zeros((4, 4, 4)), np.zeros((4, 4, 4)))
-        reference[0][1:-1, 1:-1, 1:-1] = 1
-        reference[1][1:-1, 1:-1, 1:-1] = 1
-        reference[2][1:-1, 1:-1, 1:-1] = 1
-        np.testing.assert_equal(self.mag_data.magnitude, reference,
-                                'Unexpected behavior in load_from_netcdf4()!')
-        np.testing.assert_equal(self.mag_data.res, 10,
-                                'Unexpected behavior in load_from_netcdf4()!')
+        mag_data = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_data.nc'))
+        assert_allclose(mag_data.magnitude, self.mag_data.magnitude,
+                        err_msg='Unexpected behavior in load_from_netcdf4()!')
+        assert_allclose(mag_data.a, self.mag_data.a,
+                        err_msg='Unexpected behavior in load_from_netcdf4()!')
 
 
 if __name__ == '__main__':
diff --git a/tests/test_magdata/ref_mag_data.nc b/tests/test_magdata/ref_mag_data.nc
index 2b7f60c61e7e652cd00f3f035cf2ac37bc5adec2..200f66fa5f6c43f56e3746ccbc70013b6f206164 100644
GIT binary patch
literal 7684
zcmeHMO-vI}5T5;MDYZyZ2#Q2jV?rW^kkD|z7-+W3pQVj07fpaHwvi^ZB~pwuBzRFH
zM-IkB6Azv|nt1VKOvHHe=EaK#Zzk%&izj@u?@e1CG{qn4Pj+BCJM-qv%=>2dy)xVx
zi;Oh+JN&B82Si(f^y6I9CkD>Enls~Lk%Ss}s-ARonnw)L6q)-~P|&2U6LiKj1!N17
z1831r0~K1?57^LR3<1w+7(xdiw50*43L~^*M4U^fhJ!)8JIJh%OwIr(LxAbj^b05b
zrUpe}A-FqX&mwcuTG4aMi7qE$00Y{6(7wh7J<eJ*KM$lI<@Ot59<5NTzQP_f)xgV^
z#lSG5;bamHGZiMTvMVw^X~mM`@z`wA3`Z>pT*6gqYzfYUSa{NM?1zQ@)O=>iV86DJ
zOBc+@h>^-<)48QgK4;7=7mQrKP#(HwU(MKa3+Y~isGoW9Zgzd(kU#|_SScjpwIT5x
zLVHc2VmhT@ntcF;qkln^YaZh=;@Z2SCPl6U=%$-J@W;lJK-?TV&s}Yb#hWyfKW^S5
zC@VTWe4gSg16{@HF`CG%gwVzQ7A^!<apLtqSvH?|8@HD&*umlZsj?+{US`W>TlI2R
zRmvvn1fCTv+cWVH-BRv5F5I&1*n;@W@LZs}y=?2hQns1i#;@hF_4AD$MtSvUbEmgW
z)CoM-v22UtAzB+$Kh^zhi(rPTt|*ifZIUvtInK`+W0!Nrk0+3JntS}R$~IS&75$h%
zN4TcVwfdRLt5+1@t`oQebo1kUBYd+3G55CPT!j`5`sI9f@w4#f?c{?r+L*Pk<T8cj
zRJyx?o1A;paL*~-D#{u(vonyL$mIhtI-Z;Y+|BVM!C1HkJpIWhaomXYM)twT&#p=t
z)s->^uawc}+vuB%qh>scdMB8$K~dlav@Y5`NKe~7m74TqPdSpF_8a<2LVEHe-m)EW
z1!yHb`Jws5>BSIml<dhr;)6u?lqc7+&*svdd*dSDBH$w6BH$w6BH$w6BH$w6BH$w6
zBH$wM-ywjV2)TnD5UUI<U~e8f6n)sCNLV9Q!it&Jtka<wU@ZacP^9dFy_7DLTNR~F
zJotj_&u!ibFn0a_1@%M-*7b*j-<;k^-6A`^4;J2fztC3S$kb6;elYd1zLBYClfOOl
zj*$Po{KgOfUPQ7?`f6OZm*r~n{uaHQuPoQfQ?{4oTJ!((xSX#n*UC?}m*raX_dfm|
DR;BZ!

literal 8474
zcmds63s_Xu7T&BQh&&WTuZTJxzz2%hsVKdYFgOk=h$O?Kv@Zt4koO2vnOII&G=*M@
z-=|;R^w{G{+PzI^QYv~9$qXgRuK7h%NA2Mit0_e7qL;hQ%-MR*=zLOX^|j&5-g}+3
z*Iw&iYp=a$&&p|26a0Nbe8AhA(9$iJkWeRulV6;R8jzDRZE7lvJJ^0_s`A+mVGbU~
zOJ=1aTx%a79umvIp`;g9j0bV$pnbo|5+8$X1MrWO%_}E?Y#ecFsU^|AB)Xd+1U#@v
zoLFe>?2XU{1W2t|<dVu%XC=vWmRT#rCmrTQDNT{uy*)f|N~ds1O{vhD86eFQ7&=_0
zuw;-o8MFjBJRP<%KGL)dnLo@ijMO=+%NBdOw>VN_Wr!CViL>cu#2XAG2uYF|fX~$F
z$@-aw=_xa_47&Ikdg3=*vY8>C&H*#yll2aMt<0Y`Kfj#C|7^Boo>@0Ffz8b?o>x+y
zUs}TEJY;4|O3hbZJ)B*ZpPf@QZ#+v&G)g&rd+DWXLqvWRMBe>TIC`Ht+u<mr(6G3H
zAUT8JZ?e<pOY>rJQfCZx_MUfEgp8(*9@K-EX}VK%(~_K`ygZ#lE=QT<Ff3ld(x*o+
z8k?SvW}z3t<hK?7Esc<5+-y(k7bU*;r|@H<46}<LLnM$1`9*&8O7z}*#g8p=26N{}
z*>S{kq2$NYa##huzk0#%?ni0C5@0+$XsYUIEvGd08Y#_3N_NaxrzHDgT(?r+BSa(3
zg+&f)x^CU4bLT4gsE>2lB4<TN0&V>I`cSTE!xcY{m$x%_ev}<YJQrmZXXn+)Vbw4u
zwchPI`7QAsF5~fY@g3qEcQf$4<<?78SMYsC_M|&}Y3yOXLwx)9cIwq$|7^cM1itJq
z!}mLR!?+pv&TV>ijf5}VF=P#;2*pg(EqCc#Ibfwcg!q=p(LQVcj#5V+Dw5b4(htuL
zc#^qtaF{XOkVXdMJ88!qgpV{(Iz!R*Kx--RX87O-7AYkv`_kOXzU1qX#RmfK)}_pF
z1gyNBJKFe8R*p~`Xbz=xx*O8DDV_4uSfX^AbtwjvPUX-;D5X<5au8mn59OmZl_Q7v
zRs1q6m+4d=U$j*!u5$dG<jZubkG~swUl%&nr=JU*%AuzxIxp2H(1oAMaU8zz9p$HT
zWOkyZ3Q&Sey?bocBf?i{P>WAP|B5(qRwvlswlRBVN5vY8(x7!z5p}W4NJ7}GgwHK!
zD}mE5bIY?oY~ul8aXJXSG%B8Vch-f9F=1e>Yk|<s^Y@UNFc?u60Pj^<-<sCs53Otc
z;oz1M1OLBTu-CW2`>S&YkTK`L-e9Al<<RHQ={bd#TqAIrOgOyr>k-;216V^cRdXp7
zZ=JI5ALMur*vVYCJV#D9B(dX)z}~wIK7Rf~KR)~%aQoErDrJ5W7Ie|1Ednm8435=R
zj_}?%0k~kb4xiS&^MTM^3oD)$Rr7vUmHDIicslTYYJBwfUfi|rbPL!`2UPZoFE$hj
zuNPjW|B2Jf*RmDqz{Lcs<SX^O8SOK-?R}3OiGF$4#(axy0o&XGHCEr3y;8{sm4j)4
z`Dg2Pw%`j5Kc)ywqzpQ@Jp80(Ksnf-FNJgP@or=AMDQCIra8ZQ?g?XNCs^OIF&8F9
zhx2!Z0rz2x+qt@6c)k7p4B+Eq^L{oayB=C@srCadAR9akF^7%(2VgyHW5BnhuQopo
zxOdFJ%zdSR=qn*$iu-k;oVT~{cax0o90k*xYTWun?g(U;q=Us@13rzF-;jOXV8uA}
zEq~b{_W1x>%ac_$`iJk!;e!Ma0-K;O>L7le0VdS5U*Vi(7aqs}OKzwp;AG({{wV`q
z5zJPNRq6w5&G+Jum0(Ix>pyT?-UDp^V&EneyB({PQ+a69Yqig$gCDNzkiv$MBK!p|
zh-tuA!8(3q5m?8UspJfc+5R*sO+%g4IcNCR$0ryuA6U^AHYxUy@v(F;_ExVK<-Fl9
zc^b*98DQ09tK>g8a>Z@|*BXawZB)?6eYF$UfW5`WjE;OGp1;%r+=&(@BC=_;5%Y<C
zXeNv~`S=KeYf6|D44E4iKW0Qs0zNTAWOdO%G3h99`)$m)rRG=ImFT-YHYO_RutnUI
z0jypfSGMDc`$#Ir=S~~LMgCAkA~Bx5)VAN&7Fo^LV%`|0&KuE<`Hvc3p8&$~G?gt%
zdwbHJ6uy|_5tCl1TTJ1JP7CH$>zi#VnUlNvc3NNB3+qUeiq5{i;5<JI<GlO`Xy<gV
zBn4?;#W+{aJLM(IDFJOaj#t~R<WaU0d<#kiT-SD7*Gjs(b|PX+y3?=1R>_|nKKL1-
zFayLmwGJsYqn2`!{eU~t2KOXojxsJc07+2ur0T-rjYB5_i#VUmHgkM^8rX49Ns9?Q
z&p(R!Iof?*%^WiK7;8mL4yUVZ@vj;CAF&uQ(Z%M?-uhK9{{Awsn#}NQ#OnRVMQF=J
zwaiIN+63cSq`Syb+GyNX_{FjGF<=c;$97I+Q%G>fzrbW|gSqREU({lpn|w5o+xXQE
zHY)?{gR|l5fBv+~8`q;XRy}WC_2$1G+kyL+M!oLxJCgVCsK0n?Y+gb8q-E@8%qP1u
zR5mJgbJw2SHt>YF{|uN8s`HyWeq;TrFNAHwAz()tn7eGH>}))f?65HpH7C?t&Y@k+
zYMkB4S##Hy>$G#qz^*S>>0B}7<6?G&0oHxpvo*D3>-0dr=?vIi^napj$WH?5Xw|9Z
zxA&R6C1MQ=&n<Ls@A8U*Q{qYkaF}PFJh}7-vGELWF7lOjWp25DJuCTQv^s8;JoQtm
zI&0S$K>Si|*S9MTpNNTjVa2!mRXD5)N-0=4x*NFlHfD84T5lu9rzyT1YIG-WVewpN
z{LBn%n3Cj(dd$@>v7wxIZBVp@obiR$JJoTe<WaWQqwT`jb70z~#^;5Oq}Pn-m)3#m
z7~9w$)p{@PUmV6BUwC{Dsl_-9QOCbh{@9Es;2y3U>>F&%%N3mmwbJ<^*8QHfxg*~q
zbfCRG+^_FmvTM2#Wm-|@Yt?;QpVMJ`T+i~DH720Tn1y?R)yBNB{`^j|DIJ1we^lzP
zY;W)C9zpO75P)ZZH>)c*kwFuI8>HSNl>Ex}*{Cxi{KZ<}9%+M}y8DW`{Jjv-sg409
zf5W!M-2#h#5!8O!Taj3EAvX*z+@anV_*0sV!Y14oTGjkYnaXxk#SbxjXffDtFNSxU
z@AD;i4zc4|{Xp}PIa<Vjp>7$x(4M#{BG3;^F8kp7;ew9^_grm_2+b9znx~B~0Q()q
zaLD(OnPS`+un)<E7yq6a%;NdM>Y+XxBv+e931cz8-Q_+8K1$lKRmjf)9(7RatZe_g
z{5wv#gm`XK@0&`VW9=P>L_D9ceKhdaHxEW!sF?t*%@g6%uEs4ZFvnVl6+uc;Yr?`j
z{O*H(QI55td<*V7R@`@#bY)wc^q4pMKqqk9dxrU!CwCrYQ6Gz!8k3m^f@a-QS_$IR
zYuoF%d3Z|tsYJSY;G~;}RDFUzRX<aw&vM*6+=)6$-&S+8&DrJi%vY`;F8?w?yaVuS
zsq-5@d(E2(dRIryrFToTPwzr#fBU#C$CPON+1oe%{ztmMP~P`Q_Z-T;bRQBLH{Ij6
z^*_B`qW-6S>VMilKmO=_)aN%I8|f}ZiG_6MqU=l29@ns-QJUG^uU>w?*_xp^P|Rqb
zVn+LKSMI)nH}h1gR9oVl52d;GDcxP|%J15DH|~!@ch%Rme?2{2`Ca?h8~=O1ch%Rm
Re?9$N`Ca?h8{hNy{|BS39nJs%

diff --git a/tests/test_projector.py b/tests/test_projector.py
index 6da902f..b328c02 100644
--- a/tests/test_projector.py
+++ b/tests/test_projector.py
@@ -4,9 +4,11 @@
 
 import os
 import unittest
-import numpy as np
 
-import pyramid.projector as pj
+from numpy import pi
+from numpy.testing import assert_allclose
+
+from pyramid.projector import XTiltProjector, YTiltProjector, SimpleProjector
 from pyramid.magdata import MagData
 
 
@@ -14,31 +16,53 @@ class TestCaseProjector(unittest.TestCase):
 
     def setUp(self):
         self.path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_projector')
-        self.mag_data = MagData.load_from_llg(os.path.join(self.path, 'ref_mag_data.txt'))
+        self.mag_data = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_data.nc'))
 
     def tearDown(self):
         self.path = None
         self.mag_data = None
 
-    def test_simple_axis_projection(self):
-        z_proj_ref = (np.loadtxt(os.path.join(self.path, 'ref_proj_z.txt')))
-        y_proj_ref = (np.loadtxt(os.path.join(self.path, 'ref_proj_y.txt')))
-        x_proj_ref = (np.loadtxt(os.path.join(self.path, 'ref_proj_x.txt')))
-        z_proj = pj.simple_axis_projection(self.mag_data, 'z')
-        y_proj = pj.simple_axis_projection(self.mag_data, 'y')
-        x_proj = pj.simple_axis_projection(self.mag_data, 'x')
-        np.testing.assert_equal(z_proj[0], z_proj_ref, '1')
-        np.testing.assert_equal(z_proj[1], z_proj_ref, '2')
-        np.testing.assert_equal(z_proj[2], z_proj_ref, '3')
-        np.testing.assert_equal(y_proj[0], y_proj_ref, '4')
-        np.testing.assert_equal(y_proj[1], y_proj_ref, '5')
-        np.testing.assert_equal(y_proj[2], y_proj_ref, '6')
-        np.testing.assert_equal(x_proj[0], x_proj_ref, '7')
-        np.testing.assert_equal(x_proj[1], x_proj_ref, '8')
-        np.testing.assert_equal(x_proj[2], x_proj_ref, '9')
-
-    def test_single_axis_projection(self):
-        raise AssertionError
+    def test_simple_projector(self):
+        mag_proj_z = SimpleProjector(self.mag_data.dim, axis='z')(self.mag_data)
+        mag_proj_y = SimpleProjector(self.mag_data.dim, axis='y')(self.mag_data)
+        mag_proj_x = SimpleProjector(self.mag_data.dim, axis='x')(self.mag_data)
+        mag_proj_z_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_z.nc'))
+        mag_proj_y_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_y.nc'))
+        mag_proj_x_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_x.nc'))
+        assert_allclose(mag_proj_z.magnitude, mag_proj_z_ref.magnitude,
+                        err_msg='Unexpected behaviour in SimpleProjector (z-axis)')
+        assert_allclose(mag_proj_y.magnitude, mag_proj_y_ref.magnitude,
+                        err_msg='Unexpected behaviour in SimpleProjector (y-axis)')
+        assert_allclose(mag_proj_x.magnitude, mag_proj_x_ref.magnitude,
+                        err_msg='Unexpected behaviour in SimpleProjector (x-axis)')
+
+    def test_x_tilt_projector(self):
+        mag_proj_00 = XTiltProjector(self.mag_data.dim, tilt=0)(self.mag_data)
+        mag_proj_45 = XTiltProjector(self.mag_data.dim, tilt=pi/4)(self.mag_data)
+        mag_proj_90 = XTiltProjector(self.mag_data.dim, tilt=pi/2)(self.mag_data)
+        mag_proj_00_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_x00.nc'))
+        mag_proj_45_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_x45.nc'))
+        mag_proj_90_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_x90.nc'))
+        assert_allclose(mag_proj_00.magnitude, mag_proj_00_ref.magnitude,
+                        err_msg='Unexpected behaviour in XTiltProjector (0°)')
+        assert_allclose(mag_proj_45.magnitude, mag_proj_45_ref.magnitude,
+                        err_msg='Unexpected behaviour in XTiltProjector (45°)')
+        assert_allclose(mag_proj_90.magnitude, mag_proj_90_ref.magnitude,
+                        err_msg='Unexpected behaviour in XTiltProjector (90°)')
+
+    def test_y_tilt_projector(self):
+        mag_proj_00 = YTiltProjector(self.mag_data.dim, tilt=0)(self.mag_data)
+        mag_proj_45 = YTiltProjector(self.mag_data.dim, tilt=pi/4)(self.mag_data)
+        mag_proj_90 = YTiltProjector(self.mag_data.dim, tilt=pi/2)(self.mag_data)
+        mag_proj_00_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_y00.nc'))
+        mag_proj_45_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_y45.nc'))
+        mag_proj_90_ref = MagData.load_from_netcdf4(os.path.join(self.path, 'ref_mag_proj_y90.nc'))
+        assert_allclose(mag_proj_00.magnitude, mag_proj_00_ref.magnitude,
+                        err_msg='Unexpected behaviour in YTiltProjector (0°)')
+        assert_allclose(mag_proj_45.magnitude, mag_proj_45_ref.magnitude,
+                        err_msg='Unexpected behaviour in YTiltProjector (45°)')
+        assert_allclose(mag_proj_90.magnitude, mag_proj_90_ref.magnitude,
+                        err_msg='Unexpected behaviour in YTiltProjector (90°)')
 
 
 if __name__ == '__main__':
diff --git a/tests/test_projector/ref_mag_data.nc b/tests/test_projector/ref_mag_data.nc
new file mode 100644
index 0000000000000000000000000000000000000000..58632b90d4720bb9c0afd65bec6fc46bc387ff1d
GIT binary patch
literal 8356
zcmeHM%}Z2K6hH4{#_=`gXo_WmuLy(+5gc6@q4MVW)ijbbn*x=w8FIjxksKoj!8Rq?
zt{{Sfg8CEMxQZ4+yJ!<pK`q(^)xu2>*gfywGc%4p^c5qK@94~V_uTW&x%c<GA9%by
z6dI_}nl;(y1Ck9P&vb6-5FIz`)tb?v(3tFhE+4mSibr(OE9F{~K|+_t7EqZ|Bq;Zw
zw*L(JDIh}w`++#r>Ak?m)Ca)>5Zq7zWQhs-F(J)cC;9>bd^*UyPz<gBXoLXciSd`#
z@N23hiKXD-gg?v7MQOmub;nw*0R!mJ<b$Rq*6Fe4qG&vkdW8Gm74Oju75f>ULRSU6
zY+4KqBitX2!XakDfy?X@8a{7EqQj$+$*9pEHo<=hXQ{9$ST98S&zqKipYV@Or{;9_
z8`m?*oDmw(6RC7EGndL{^r`urp2_BlUDx7QQ}L;pWV=q%$)b4ITEB2epac>x>p>yC
zGbFxx^j9Pz^Ds7<Q~(r?{slqTG|vOvdS7so7glJahh6B85PLUj44vbl)^NEpF}R}T
z=Cx)fskHb!`AG)4jO;NPNi761L}iKVhI^-%eOqaxl}_3@ZPun>^QahYJ~6h<v;~?i
z^x%(ZTfXy2E7G<o)~pR}f+z4SAZ@E+5#5p=rYlcd7rg0uzq*w+vCzbE!*<b*2T=KL
zvktyW()L22x|z0>zoBiSy?VV!TPHv0VURC`^4%tQ0?&1%ZB{Izr5^cP<!K9onICyo
zpe=+WlvPQhG0`DOyN>AGv7ghdZ*$LRcmpXXd88kUjBad8>QRo)6OoE`sr{YGkFvY~
zcZI<1rU&PGU7P)dn0uS>U5O?O>g8;D_PdDZZQ-5NTb+(y$)s}giDX+9cRBmC>VZYw
zBDNK%Wp^Mue2N!fcsLpZJk4=!V=kNn-u@JmG#<oiHTz<su}^6m&82NjUfRZ(Z^N%I
z4jQ9jv|Gu94>kpEK=Y#0gY0zd6V+rVe;Om%>Aaz(LUxKnONs0hM}Srs55*xr(zG!I
z90h-hkNBXFKgH)(_FY?eavxj-Tm)PMTm)PMTm)PMTm)PMTm)PMTm)PM{yPM)5>Z}L
z^NU&fXRtPp6^ah5P>h)a=9n2V%t@<4(alN%SfNP7bMd)ku2`xlRN}!GV1I7&N`SuY
z|1YQ}La?qp?)hoeM)oYTRr_G+&CfH9dz+amD)WzGU-vdM)oilM6CViqe@ox!1t|T{
z=t^z)Ok!uJ{@rT3I8N=m&9`6uoZ@Nc?-W0~I8N<O^X@l)r+C`=JH^c|j#InSyp{9+
E4ZoKf2LJ#7

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_data.txt b/tests/test_projector/ref_mag_data.txt
deleted file mode 100644
index 32d452b..0000000
--- a/tests/test_projector/ref_mag_data.txt
+++ /dev/null
@@ -1,122 +0,0 @@
-LLGFileCreator: ref_mag_data
-    6    5    4
-5.000000e-07   5.000000e-07   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   5.000000e-07   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   5.000000e-07   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   5.000000e-07   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   5.000000e-07   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   5.000000e-07   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   1.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   1.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   1.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   1.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   1.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   1.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   2.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   2.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   2.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   2.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   2.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   2.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   3.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   3.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   3.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   3.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   3.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   3.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   4.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   4.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   4.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   4.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   4.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   4.500000e-06   5.000000e-07   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   5.000000e-07   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   5.000000e-07   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   5.000000e-07   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   5.000000e-07   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   5.000000e-07   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   5.000000e-07   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   1.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   1.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-2.500000e-06   1.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-3.500000e-06   1.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-4.500000e-06   1.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-5.500000e-06   1.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   2.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   2.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-2.500000e-06   2.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-3.500000e-06   2.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-4.500000e-06   2.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-5.500000e-06   2.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   3.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   3.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-2.500000e-06   3.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-3.500000e-06   3.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-4.500000e-06   3.500000e-06   1.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-5.500000e-06   3.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   4.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   4.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   4.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   4.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   4.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   4.500000e-06   1.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   5.000000e-07   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   5.000000e-07   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   5.000000e-07   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   5.000000e-07   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   5.000000e-07   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   5.000000e-07   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   1.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   1.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-2.500000e-06   1.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-3.500000e-06   1.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-4.500000e-06   1.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-5.500000e-06   1.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   2.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   2.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-2.500000e-06   2.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-3.500000e-06   2.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-4.500000e-06   2.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-5.500000e-06   2.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   3.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   3.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-2.500000e-06   3.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-3.500000e-06   3.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-4.500000e-06   3.500000e-06   2.500000e-06   1.000000e+00   1.000000e+00   1.000000e+00
-5.500000e-06   3.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   4.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   4.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   4.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   4.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   4.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   4.500000e-06   2.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   5.000000e-07   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   5.000000e-07   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   5.000000e-07   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   5.000000e-07   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   5.000000e-07   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   5.000000e-07   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   1.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   1.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   1.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   1.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   1.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   1.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   2.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   2.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   2.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   2.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   2.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   2.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   3.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   3.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   3.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   3.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   3.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   3.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.000000e-07   4.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-1.500000e-06   4.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-2.500000e-06   4.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-3.500000e-06   4.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-4.500000e-06   4.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
-5.500000e-06   4.500000e-06   3.500000e-06   0.000000e+00   0.000000e+00   0.000000e+00
\ No newline at end of file
diff --git a/tests/test_projector/ref_mag_proj_x.nc b/tests/test_projector/ref_mag_proj_x.nc
new file mode 100644
index 0000000000000000000000000000000000000000..025a3c9090a0e73cbf412772d985cfed6ee93b46
GIT binary patch
literal 7276
zcmeHMO-NKx6h800nejP}IhJCX;Ohs3iZc8`jBsRT{Hbw<90>|cre@NC`H>tYMX*IA
zxhN=zprA#YHtm9N6)jvxn-)dTqE$h)jUYSc-Fx1QGY(}jF5(>>&pYRyd+xp8ci+8w
zyg3vYsC75HRb2;`bt$P9;c1r*x1ZPaMuq~Ts^^)y-}Grt*~DFnwOa*+LK<7Zfl5=L
zID&1@35wG|g$DeB7}OhmAm-$Q&Kl@k*8o%nh2l_H3+tfI>!qh17)9b23V>$_FdiR&
zX->bkN>OkKkxs_r$U?LR%3Qy%)toSZ0ZlqIEn%b6T#M!w13C7H_&c&6uTU$$!ow7*
zfeV*~VE9A*(J1Uf7foEnPhfZ?7>*7{!V^({e<%o^^R!A0mtb}X_m2e4c%O_Pn@Y|Y
z_#HDciJU($V8oN@L}n(L%@`MFb4Dhc%Qwx%t|Vg@Q;DMnO9y6gt+jp>kU#+>j4bQl
zL!$4*WXLX)NnDdu02G1#8A;ca7zc#+p5$afdZCRU{zP9t+Y|8*offIq<P!YcQ-I;x
zO}64-Xq*cZ2)at_p`1$2cT$SV5_cTtK0Q59N}Eee0{54Yt<z>M4m($jHeJ4r8)@@4
ziwXY~ZIhEf-{)ytl#4Gzo8$?c^F-UK+(b8&2kFYw)(v-$EbFDT@d3qp>$I6?StV(E
zAyM5(+sePtrvJEnD^FX8IOt(eYy>{s`E8Rtf%7WSHZ74_>Q%o~p0+M{-Su|WqAfr(
zR2_=KbK;YL-#Uv%xs<Z);+`=!xo6yT18IlE3G{6d4_C4k$6k?gN~YwuwEUeaj<N*_
z?uwidbo0a3b=|R^Cxp4HiTWyh&V%DpHa-1S=5v|9Hz*Entxm@#Gs)a+JkeGqLQX!e
zx^GgqNVW!bcn9Jx&R>9`;piC9X-;<|jYVsq+n;l?CM+D)_yfg_pF*45g*FXdXj9HN
zp;zYy{gDv)&B0_0nG!ePb@A!Jem?ejE3lvAd5-Mo^M<zw`#BG9A@*|~37Rz@&cku6
z`NfcMSn-^n@FC%N&M&<9v(9?5A8Z6{1Z)Is1Z)Is1Z)Is1Z)Is1Z)Is1Z)KUI|Qf_
zQCz|H$W?k$RGX&?MLSg}MuP*v(O}pgoG>dC-B=Q!3Pn7Yi_Ii*`BH^di3i<_e{PFP
zfU)WSFSsVcu&zAn{chGqwrymy_JP9(KcpJBcVw=p%sw3ZyuBlH%_h5i;T<E7+9#fU
pQVR>+XkY7NE3e64980YfTkN-N>{BArXT`_>vMV}n$wVsY_yt8v?Zp59

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_proj_x00.nc b/tests/test_projector/ref_mag_proj_x00.nc
new file mode 100644
index 0000000000000000000000000000000000000000..f869a6319e79d9343ecfb6852cf29aba53261899
GIT binary patch
literal 7204
zcmeHMO-NKx6h7}~#&H^REX6XxR|$fJ7!DFfsLVY78Xa;ZC{P)jK?j@}$x(6;EQ%zT
z1ra2)Xx*+&t7;*%Yu84C7Oe`ZMVlb9bKbpY<~cf)#kh!f^v!+u+;h*p=lkx@c)U9h
z>Z|fLcvX)FShl1zHwsO=Y>515y%`w@jjFz9>IqAyxnvV}DK5P#C}dLC2s##;0>u_w
z_njj<4OFPbABaJX(G6lwJrFnmfh`R{RZz$dg-cOu?(zHS?I2o_1VsVBGXxlqkH4~}
zUsa(fID~K~?QvuwS}l35Io@PV7{Gvf57e(<qsxj#^NNA=qr(24?8gymr6W8=rW&|0
zECeGM?u|y_Fsf+cDn6mXVRIxp7#W$227ALM_^wct8irtX80j50E&Cp6KQ@z`H}E%R
zq!Za-sLzNeQ;GC^GLtr@7P3Y<lPxqY#;zq}Q?rRSgQWw#xHp<U2uQ#N2_wtp?;-IV
zLTAV>lSy2YlmQfh{zXaGj2H)m_JQOiC$-SRFMm?rzH~Mc9JnA{ZO9OU+*5$zK0<Xx
zXJ}lxNd#Rb_E3hBO966GUgFNc;>V{achcq-lfd;QWbd?D!C~jJ(dLo6aXW4P1~K8k
zqHTKm=eq)J%QE;9v`L=8wM4W%my76*@+eh)+B)I>=~d58+IX*Uxp&&EgRh*ly^yGG
zr)}+DXq#xO{8pf?Lmc$bFBSqH?!4P1PvE*vw9Uyyw9=)1E<bGnFmpq%^R$I%hN`Y8
zJSRR0c-Q4=l%bSt7x#>@%RS?z8%S#vp1v;Q;YxKyKPFrn(k1WG(s!yj%5oCiH8~^b
z<d?mdZJW2&33GQn^;I}n(644vb6=!CxAnb2(YduU6`M{cvkUP=ONB5w|D@ufMcp#h
zHK@ir5O3td1sEQTjsczKw6<w1ih*u__Q@q-p;zJuiWeVyoz(U^4Q{WK&$r<>m-~Z}
zFzKzqqzzRiZoqNz>A`wF_IW9=p6z*#tmpHFzml+?{qPcEJ^PWM<>$kG*p5ry7!r=W
zJ^LqoNZ6kJ3oU+D=ii(c2LT5G2LT5G2LT5G2LT5G2LT5G2LT5G2Z8?%0m?)aXRv)T
zO5ZHy<|#wbP8o_(v(FqgM}p?0m7(awlmKNY;<0ROK9Mb?D)O0l@c8l1ZIKBucK!ba
z=R_FRwZ~oGt=!1IjcnyUaCr0mY~B8j%o&x1^|4R;J2K~NGOH7t05`fZeB1L9-$`eG
kNN3j<*V&fzV0&0^*O3kDxg?!kUtG7_L6Lu6JLM^V0k$FpP5=M^

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_proj_x45.nc b/tests/test_projector/ref_mag_proj_x45.nc
new file mode 100644
index 0000000000000000000000000000000000000000..3a342a2b9697a427134224a09cc0472742c49ec1
GIT binary patch
literal 7204
zcmeHMPe_zO6o23M?XK&#{wu{&!Os*V3o#T4!*-Y5&ws6Mxe^trthwlduA-}?DH8p$
zyjc)ILP4hv9fF`dRfN<b=+;Go4xI|JLzf`3c{B5NZB@!*J;XOUJ2P+IyqWj=&7bvo
zt;yGr;V5vZb~{j7m8q;qNUCH<;Csb|K$EXsbv{z}a+zk8T{I-Qbf}=9N?s9ISfMG9
ztU+z(Vbs$=g<SRnA;{5PAlBrDnjKKHssX49OQ^>Zm7-PV&d$bKCey;?6$JpT5TLWS
z^EqFBMw+6qDTFzx&t?`$%SFpMCB=Nf06OH`A%BK-TDdQpLri4ZBlNG!apa-4xrh5v
zRRbGyi-F<wdxAmO#YDJpm1SRZtI-y04zzU#y&k^-&eQ0n#$4b7+B~fW*LO?(j{eA~
z&VECBG#vB#8uZ@CU^qG&8H(yXV=+BC6ialChejfyo`G<sPN|w%u`LyU5RkwcNC;Uf
ze-DX$Co{&_B{Gp~Oep|GpnqJ_)i34&A-yR%nUqo}r^=s{mrfiCc$<z2Q%lkXFO3vn
z*mh%Eu@E%Y$!-R^itMq}5}Bw$6R9QcAdG)}P`aKrn^*+fUm|UtHtrl7oHE+%@-+TR
zTXun1@L$o^*Z1RHg0?B?{3f(Xp1?YRv@Oa_bVa#6n0nf3;l_bk`+C~wtWddi+W5tn
zO4^=ERDY#y?q6u@s!acypsiY5^w20a0$uKO+9Xe4J%_Xn%S|*>r+!X7Z8cy_w!Dhd
z=ED`L7Db^o(M>?7E>5F#Wz&A~&gdJwGY))#v<hMA%M`m@v8`ANg-KnSq*J>2n<}od
zNeS+pToKe#W$R_-^2G(j+?J1H6>=6VXNLxdSEM}~|K6Zjs5gBu)EAA!#(Kl$X+ov`
zUfL~A-4wPp$YO6Gdyx|lpuaiT0eG9^-p0Am2Ymg>CY6YVC7pe+<Y0Mi8^vqeIQiN(
z+I;JObE?rB@S~hhCUvkWaRc&;ZV!^vwNG1t<fKn)BstwT^p%9<WQVp8$;plcExsPI
zLwZ!w!H{so^~pZsgNgLXzL2udta#0=m<X5%m<X5%m<X5%m<X5%m<X5%m<X5%m<arL
z2w)^4IfCt!UOERbHjfdCDvVIH8x2Og(dIR}d4!^tg#<7{(Hn||M#Hg0s3IPT2YWXA
zb6Z3L^bP-iK`{}6b?#2xHy#_=wwZbCgH3O|AIRI@nJJ<&w$SluduOJY&CqPua_7kT
zBZNNIjbry+CDS=PM@l`e$1h&Gp4=^TV_uwIabc!5_Mr~x<Je?-i?1KX?PEQ2WW_cA
ntd-}b`KzvaPlmfW{|wKUPwsPLrZ)DW4(a3AWP7ysRFyvgVAvz*

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_proj_x90.nc b/tests/test_projector/ref_mag_proj_x90.nc
new file mode 100644
index 0000000000000000000000000000000000000000..50665b83db590616600411323503e265c8289d63
GIT binary patch
literal 7204
zcmeHMUr3Wt6hGhhZF8=fvlPn&ze*4+#BfMhvdQ-Ouep(vpg?7|p#!%`PRSuy6iN10
z5J5r@J@?j&Ptk+WTW`Hc&_hoJ)k7~qW#@eNo^AdaWicP(J9>A&d+xdC-t+t2@7{U5
z+aK($ay7VArxRE<r8GARO}p%fd~dxO=?@O8?&s<WQ>WQw7Z2H9yHrrfq^=QcSZNAu
zx8Sz>9NB50LM{G43~F>Ah&B1adjPzf8i1;xkR1xwqSfs8c<AXMT9E`q1;8r=7>$p<
zG?!mhp(r?oa3}3?W+7TFd9FF$WG)y$hk7T}FJY(Mj74*aiEKxO{e3x(Bh*Sqc#2Fl
za9~&nMj+G^jlyA6(ZW?M!GR$o936;+$D@IskOA&16s3kCm;=H+LxySZm-Zu5$yptL
zV|qG~4Fr4jcrukp&n7czePS-Fr!(0?*L>_+GBz=tXwz9b(2HZe>AipiiXdTRx&A#Q
z&O_)7*<~_`Ymzd6BGA7m>6#MrfY3gaoaCeyTKM5l>f4vjMgsj8gsXKKLV!mKFdRpy
zt=JeEdu|*-SBX88!Q_IMT$Go%Gcf<**~y)>Im9Axe+k(;ZDw#dxNNjJ<=eQGHcx|C
z@L$n3Ir;N#fwn~%d<ohlPhejl+ScSIx}!W!m7lgwcyM~zxsx`2SGe9gZRWvOPTF2b
zRJYQ$@-MWFwN-vC(AFUidgv1yfe&|n+ayn5zfQEx$W64=rG6?uZC)^PgRk<m1!;w<
zO;LDFd=l_mm#0yNQnFv%Gx{#~jEim{tyOsXvWSN(wH4bj;Zm0_`7JGdr;4L2C&68j
zD}qjb*n8c!ach+@chu8Zg@Xm#)l6#Uv-IaMzc(m0?yXG4Cez96Ts+ZIAxyfTRy;DP
zTcoxI)p!Tuja;|@Lj%zfpwpc8HqAvb(CyDYxh5=ZmH2_;!cyEOb#a>}FK(01H=);;
z`vQ>=>CMTc4K*chz;W^E!FoRSc`LA<?Rkx?=ktcYlCYlr@D^e{`;nmK*Ta6;j%$7~
zBpi8r_D}ebus!=1TKufeKUohJ0u}-m0u}-m0u}-m0u}-m0u}-m0u}-m0{<NXl!@4$
z!FJ0i-P4qtrwm0qWhjP?USrq@2aItuL(z#T0m@LsW7*hjB3npR<TLT$^x&V{A`_tR
z`u_{gi7>1yPrAODxsiRF+01?5^v1jCy8WG*Gb(edBOmv7X3p7Umd7>-`PP}W^IY`W
sd))vxd|Z~iU4CD~4<8jY^;6+C^|2k*G?x8Pjkcx~>&oGu=P5`13#SbgasU7T

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_proj_y.nc b/tests/test_projector/ref_mag_proj_y.nc
new file mode 100644
index 0000000000000000000000000000000000000000..d84e528d9b677641e946acf26c89c0f0d3d4b341
GIT binary patch
literal 7204
zcmeHMO-NKx6h7}~#&H^REX6XxR|$fJ7!DFfIP&KC*W{2RL4nHH3_9S<NRE<&U{NHw
zEQlbXMeBBLT15+?UAs0Cv}jdOE!qT;o%8NJGvm*o7ULq`(Kq+qbI(2Zp6|Oq<MBa%
zsJF`7;8i^yVA+t;+$c2dvLW)T^-iQeG_3kws;6w7=8{d^WozkGK_Qd6M$oa)6xeFP
zb>9WD(?Eq<{DBzM7~LS|6okMb2yAEos)9mxC|rtKbI|Xnx5H>fVu}KQX9zGFAAMs_
zzp6q}a0uZ}+T+MVv|939bF#^vFn|H|9;jc!MwcCn<`o0!$A$ev*^eXCN=JBxOf_(0
zSO|t0?uka>D5_}UDn6ls%hphIATl%_HG9Gq_^wlw8irtZ80xuf+4ezcKQfh^HSjlP
zq!U>))N90(sYH4<nMoTHb6F#u$rhUCV>gqriRna}!P0?V+-psr1SC)d2_wtp?vQwn
zpfhBb$t12x$^eQ$|B9q*N{j<S`&e?4lUiuum%WttuAYyW{g;HRH5r1*Jp~x<V^mjk
zhQ^f}N6=Mb4`nd95Fi)jCGH%|e|~X#J8f<;30z-7_D`D~9Cj`nZ63KBH`C^C5EK3@
z+9oG|e<;wlD1$FSo8$>x3q;#1xrpv7PgCWmtrH%dUG{9JjrR(d`=`x5_{vG!Yl-S+
z+E)IBwz0O#9|hVv#6b^zVj=M1&bv+W1g={|+l*X9OI_;M^3xUoD>wKyPg{s)sOpNs
zbK;YLcU_)F8A{1^anBe#+%sOffwWfP>H8ucu2fg_6T+n-UGgq1eW!|}EGNNTku!o$
ze%XK7wtjb&Fn8BeUxkAO{YEA=^G*75+us`$om(qYvB`8YI~Py1R0xxc&nuqT)Gbn7
zgKE43@kTCOfZ>7Y2+(OxYn#TR80hwApIj0adL@3Kc=0K&le)N0gBRDy=lk%xYkg)U
zOnQ4TX+u?s8*p5Fda$04eO?NzXM3I_>-oIluOzHzKfHuk&weCm`T4LPw&RjFhJ+(;
z&;AJ?61HdmLW`f(`8VgqLBK)4LBK)4LBK)4LBK)4LBK)4LBK)4LEyhbfHDzVGuS>E
zrEi*Y^OT`zrwqlg)oTq~L#8!uXDB)`B|sU9cq|*6O=JtHihL#>JbwIhTVw)^9shs9
zIT40+<yqHHJ2!G*Bip$T9NzjkU3ah}b4F!ub>z#zj?6im%<|YeBfq)4-3`D`Fo<m>
V9%^GdD)%bIeahi~uk+a5?hlnL@nZl0

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_proj_y00.nc b/tests/test_projector/ref_mag_proj_y00.nc
new file mode 100644
index 0000000000000000000000000000000000000000..de3f39f1b426fad4954e793170c8ff84020d24e4
GIT binary patch
literal 7276
zcmeHMUr3Wt6hGge%{gakDV7O-{Q<!u45ySOQ@+hVH8*k+RG7>*a<G5olpKN)kz_<r
z5J5o?z4X#s5JD6__#E}nLp{_(y%<z)(L>lh-@Rv>(_t1{5Z~2r_uliJ@0@dgzx$m#
zkJtL$y_NO`yKJ)owH48#ja<?yZrprY(dqAZ56O-v@@_+>Si~)QB)_-IAfZZKBdDxX
zB*@pGx#I}xDIh~F`+*?TXfEI}b%V14oGS`|EU|`qtWnQrd%LTvakPbLVbggBKoJ6r
zghm#P@GHwCiMinBgg*1kn^KFGYj!mm0Rw1IZ-e?-cGF_4MX_^7>JF}dOFTy_R0^+f
zAF3)~Wy@k<=-!?{0Jbp^1}?M9Juv9;1qS@S(SY9L^?>6vu2Nx3Fdp#r40;UxZlOOs
z9+}YCZ%~VeQ@XoX3q@k#_(UWT*TyDOT0D`;-kJ(thy=%?;R71AHfF`T)bx=<0y&Tn
zvea)3iES%0#@Hn?k!x&40EMG}LeMqN{Q#HV5uBuj6q;%9JN=cDhx~f~ac*i!EJ3Fy
zIT+UMIG0p{#*!Xopv%Y}YsVtfPBc+m;ts;p^5Z>gX``J^{pM*iUJAC3qS0m(v8|@9
zy1_sX{)o1@>+kJZ+GecWQ~}xqPhgow+7`qnx+>j^6`!_tc-8)PVJ&SUVwM(o+dOT?
zo@5UfjkXsew$-%F{|#-=9-RqiX=~$&9{L0ZBDvGsCU^qNMWih$HqmUS{H6G`b-?ZY
zbG8g^Zj4Y?C5d9nWC9U-hDI?jc%Pp$+B)Zq9ha}P@C@{Ah9y^=OX^N;a$J}sx(lDF
zJe8#dxbq?+=%B&o``61iB8a)Q9-oyc&x3j{5lem*_N>PD21%v6<+0%TcqBC$3OAQ=
zm9B?ncMa-haIQcVI|Etr$sT~-fxs|eHpjCOec>AL^e3CtBNl2o`(Vw^uG~3_bLZ$h
zcaApScwe6C)BRqQ8_t9dP6cj2>!R#Ia!UKO6-Z9{6eGzg-%x@kIoYADLvpesK+D8K
zc1VwUdNBkX8GW*k_+TS_vd^XLvnn$(2POh00ww|`0ww|`0ww|`0ww|`0ww|`0wx0g
z9RgU1$gf~K#3~(8tj%MEq7^F?L!MsGkjJNcMvV$Z2P+9+g(4J81t-F(Y^fqsi3eLX
z`*WLD0<?Aie?c`7f_45u=MSScQZmm*?Sr}Ze2CVS)-zR9Chre_F0E&(*(B!9zH3Py
z?t1R~?ppkG*#&UPHS0{fT9OM#@xn36^5r;pHT*O#9B-sgcF+g<LZAQ2FYUBa_rCz|
CRyY#?

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_proj_y45.nc b/tests/test_projector/ref_mag_proj_y45.nc
new file mode 100644
index 0000000000000000000000000000000000000000..4169d4a36c37235db201d966162407b6d9bb9c43
GIT binary patch
literal 7276
zcmeHMUr3Wt6hGhhZF8=fs|?Emzy3f`Ayz6esc-Ua{;8?xB&aBvIdV0p<R&G84~3Ez
zL16?1Jwy>fFA-FrL=QqQ5e4;75B1PX7S&q>LEUrjJ=?TKS<Hv{u72Ko&pr2?bAP{Y
z=j!oPy}!1=RpL^ePN4Q(w%AG`DVGap?=J8**ZW&l_Z@XLmuYsnL|bO}E)^71S-KKz
ztkV>jtwD2lHR@@gLJ|9dFf7sy5Hb0nVgXcq*8o(7HPmB`dNEt;^LTKykZEBvMF&6;
z0_^YH|B#1Ykf$ijg)k@enP<_oBDB0{RVfb`K!;){6c4dQJ3ou&5{_)kh5k9Yk4~s%
zKjB7H)xg1y#lSFwHK7nJVImA%Wskq1G0+rhXl`l`nKi)xxcA~IHFgBPp{b@Z!1aAn
zzpX3Ur?c;{9*-nUf34mb?T*CzqCIiFBbm_SJ&E*EfB0xL+!2eE>D0C|D~_qsw*nH#
zfP|2x{`ZhL7c*mwT_O{?#+Cz61o|zKt}fvRg!F>sWI#$`9S#1ZKe>Bbvsu4On3|GD
zFlna%!?6tKijAPL541DTRb-E~ozdeJXd<`7ZHE5oTWfx%jox(X=S~~{P_T34j5eo?
zZ6<A=5{@4H6>TG9uU%={1|7mw7TP3FU_Xwu-Ip)XuyUz8_q0{Q)5@3kf2K`F%+i8r
zbEl2JlWcR&XnP=In@QW~ztA>uV_zgq+g6e2p-y5TlRJIdBu`*JhP3s{muSeVe#|{>
zRd9aOh%-f-A0t$4ib64^GJy;|MWdW&T^IL^{)>CYg~!)6hz#_3kR?}~E4CHFWS2Ba
zbZ5U)MJgMR;Eu|Opo#`_@1ISdi6Z8XV%)1xo(J2(p6=dH(w>9=-k{iMHNQK2C>~8D
zJ0t7zgv$1-c^5f#gE-fqklleS`J^wvU_+=4Fq`Auh`#U)c>9x0>JbZDKKsF%i#?fh
z6lc!SdFC8#z6d_qQ)e~@QO=!79h^$sfX+qPgXEO<=~W;(=~Ij(r+h;Rn&f1MULBH?
z9SK@09<oDv)YAt;!jaM^`-l%V(kJ^u%6=B6M%KVWz(T-6z(T-6z(T-6z(T-6z(T-6
zz(T-6;J-rvD-p9R*lu}BcMNOuSfMD#3Po$6HqaVqG6U_rLQ%y^0$8Ew3@5^Ukwm&w
zk*dUl)5HGU7L@?~m;b+@nh3!<dd2&d*GA^eGp~Ix_klODrSt2VDk{m#ZSUvTGu3Q*
zMh?6}<Zn(~H-^VsjKqgJ-;sOo3}R!<`^b3S@{O;d4#`h^u^XrJ<34<FOmeb=dvGtw
z(I(nPADMQlA0IKMwiFre0|mZ!=Czc*Iex~NJbat4p$^$8OHS_C+p^w=4~|Jrc5n~w
RB{|we+vp?H4z15;{{xySDcb-5

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_proj_y90.nc b/tests/test_projector/ref_mag_proj_y90.nc
new file mode 100644
index 0000000000000000000000000000000000000000..b02f7c3570158c76cb0c5e677ad9f44ab020407e
GIT binary patch
literal 7276
zcmeHMO=uHQ5T4yl(lkwNsx8%8==uYNDpKi>wKX&)Nq;m=uqlE{4XFtxkUwl%Y$+5(
zEVhWCAcCR?4|?$?NI{eydJ{ws9`v9GMW~=2ym*lMX5X76{Rvtn5oD)L-oDv+GxNT0
zGcTQ9>T~s!TWT$`*$gDhLSl7X(<E+Oc~sWn>vIjt)`#*=O{W;eEn2dAw8$W#Np&3<
z*q}(T+JffRPP9`%hAQ?0L8w%_fam0ZmNIBrRsdv)3EDBCk<WHJY&N{w#H>)9d;p*f
z0fs}v3tIZ+C6dHKaCgF<MdqSZq36mS^;*IJD%6;vW}4kJYI{*EJdj}<x4$aZ(GHdT
zJKTe&3Ygfo7#L1Zx8Dz2m<kh@*~`^G;P(3aecpiI+3j(I^#txxVO!7^c)JJOn!Q8V
z4@IJ*D*FwpiE!HK>QO_{csMZ{O(xWlv9y{<rn9%kgJ+|`kyv=2O47`{m=^2bb4VZu
z5<-^7UqfQv%$zZHiA>}gr2wFC^p6U<B0LUo?RCM)q|ibGUHr~?;aI!R*>{AyS`=Gw
z(n=17X)Dep13_b)3^34TWRJ<==tK*;C@gXNVSMSquC=t$lTPEtY12LwY##-q%`9?T
zNt>-!Ll6FlwwbxNmMm>kChjT^ZGtB-P9Sab;vu>y-HaEWwpMu2`g(pXZ6ae<7PxGj
zHtm^Yiwj2EQ<2+B+GhWTw#WBRhO@La^Fj~30s~Rp>C+~70^>QPEhQeJ=??jG;c07w
zYkOzR8QNT!p=^*O$|+L`MCutD#XRqQe$J@toHG{OzS77m(AOzeTyZWLwsV&e;gaai
zf2Q(MHYvcJ6&XPrU2J@OxpX;-n44;FtwePm45yRv)ED8;q<wFY40N|N9z2tXrpH3z
zh7xXKzgu!cqizc43RJK&kQJZo0qE)X4*^zlJR30<?g3AK@<}6NVJKy9Of2k`J4bcy
z9E0c1(dR4A^W(ivp9l3?FkypJfg8}isCtl|%04{`q$hjIk@Qq=s6dmR{LrIAdh#Pc
z%j83T$c{$(U<f!e_T(S&K_Pqc&$aBmB6Fo*=m_Wt=m_Wt=m_Wt=m_Wt=m_Wt=m_Wt
z=m`9G2w*2-bqCujc4>`aZyq}oP1vCrboaOi-Cn0VpmiwPSW5sq6ro@`I2ul8TNRm3
zJeY0l&u!ibP}lwc1@%M-*4aB9-?iRI(IRWT4;J42E>>OK$kb68yEXKwxRI%6lbkvA
z29a+}9kl<5eCuj`_R;>td%&Sq&Dl|p<L!lZ$HIs6U5%;vL#RVRec)cF17Bo|dbB}X
ZTtj*s(I@)G7#Is}QT|n29=*8lKLO@)D>DE9

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_mag_proj_z.nc b/tests/test_projector/ref_mag_proj_z.nc
new file mode 100644
index 0000000000000000000000000000000000000000..fbb20504c9cfee6a3117dd80ecd6e8c62da3eefb
GIT binary patch
literal 7156
zcmeHMK}=IY5S{;jTS_TX6oMiNUo;`ngkUH{F@}=%1I5xtN=zgHQfwnlXiKCJX-M#3
zB;w73G10_>@#xJ9C*y&{oAKg74|?#TCdz?}C!G1a(^jA=YB4c?q20f;vokyI{hbZN
z&4EB)wX4ykI-S66Q?`x6LeefLT7svw$UtCNbw5#COqphvQ@mtr?@~b_mHH;IVW%mu
zRfEUwGo+`13U&AcF{sshK<vo}-YW2JY5=N&jr6c_FGk0F9uGb3LoI54(E;!d0mkBE
z&&=&tS11a)5ay&ldKOKqBg?f%n#~OZ=+NMVh9#V|o3Usv;mCGK=--y>I6|#-geOQ<
z0|$nMVE9A5(I^~15p7(>ParsCgrmVocp~cW4H@7br6@HF!CVmT9WqRPpVS|jO3v!|
z8`IN?tUu7F$CIf<dN!F!>yvX?J)OxGrsiW;lCjC@M4Qg71FbmLn%@gZpa>F1miwPW
z;@pqMkX<H|xTaPHPz3ttC0$d(4+!ZU$w^L1p_LzgrN4gRRK!1UPMBJgA^3Tv0K;*R
z#)^%hvF9cbbd}h{Hke%Sl8N#XcM|45K0dmgHh$B&-#u;Sp};uGMw?Ua?R(lhjV5~V
zTeL0Tdh06Cw&)P1O3)^G0{a5dwkltu8_K;@`DyEfmz}Ryx6{UFh5OypW+uLJ()LuM
z`aNwce?r@MTjfT9whoc#p<l4a$(>J|<O%FoiMAQ}5-oMBpUY317mVEC^E_<<+M#Mw
z6y8%l6UeRSX_VuV^WvV-cerO<bOUL}g{AdHOs+ImZ1Or@mzMdImcCO(D$7Z5SL8bA
z;)mV$Rihss5$28tTB~rdV7rt_&3uve98F@9XDd^&%jslxE}m$u5GrRMR@^nITcoiD
zHFyVN@+n+^p<r|bD4Wx}O};1wy8YQE_XMx462Gvy@KZb{aq*a(7mvy2tI&&!{r*UZ
z<fb#JLqmxha9o@{Sk7smUj>%4KJSs`oNqWmvz+b7Q?|{o1T8OTJG`EIJ{S^?ygu6}
ze5kQL?^8(eyC(l+Jy-}>2v`VM2v`VM2v`VM2v`VM2v`VM2v`XGcL-1=VrvE4Eu(Z#
zQ*E9q6zx=@7&iKhVI%A}Cd>*&7nTI5LJ^N;W3!2Dp;VEt#Dmj=e{PFPfWG7ZFSsVc
zu&zAl{%Y1n_RO<c`#|@`yXpG9^~@EOx%(rZ_SQ4kY%<H^Z!mNpz_p$qe@N$_>7nbt
G*S-Ovr2YK>

literal 0
HcmV?d00001

diff --git a/tests/test_projector/ref_proj_x.txt b/tests/test_projector/ref_proj_x.txt
deleted file mode 100644
index 94ce2a3..0000000
--- a/tests/test_projector/ref_proj_x.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 4.000000000000000000e+00 4.000000000000000000e+00 4.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 4.000000000000000000e+00 4.000000000000000000e+00 4.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00
diff --git a/tests/test_projector/ref_proj_y.txt b/tests/test_projector/ref_proj_y.txt
deleted file mode 100644
index b8a1448..0000000
--- a/tests/test_projector/ref_proj_y.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 3.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00
diff --git a/tests/test_projector/ref_proj_z.txt b/tests/test_projector/ref_proj_z.txt
deleted file mode 100644
index eec624b..0000000
--- a/tests/test_projector/ref_proj_z.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 0.000000000000000000e+00
-0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00 0.000000000000000000e+00
-- 
GitLab