diff --git a/pyramid/__init__.py b/pyramid/__init__.py index ad7b174a2c94b02c324c649df6a51f502bab6a3d..47aba58821b746abcd170574a4a3d3b64c66364a 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 5faef3196c67b2b28bf60f54d894cf88de517f3d..3585c897cd0da6f2ee06667e4444ec532d2860af 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 50c41ff688b3231e6dc916e6b3d6ca5517f8f0ee..fb67e4539166f37a49e046c0903fe4d6163b7751 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 9f422ca59f1e6cda07006b5dca9bd881fd82bacc..a1924fb1aac69d03a3802053dac09c558b81c533 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 fbec1ff0b72b94a88643c1228e2083c2d554296d..b9d84bd1db845cc3e867973058845a38e972768d 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 1c7733d1427b4a973532069563221d76dc3e7358..5baea7fe47ee6e90ad1234ed0f79534c581e98fb 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 f2758b90fd2eca9287a8b754897f9d9f73ea9607..d7f0e6e69f53de8fc12ba7314203acee9ababa5a 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 8c322c5f788bd636916c62423f2b8fbd8bcabd56..b8bc451f628d96df658fc8375932331f98df0532 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 c3abb46ee1e495976d1ceb1eb55bbee57b75d4e2..3083d459c9c5b3bc9895cb15b39b2afb852b2e37 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 7fe3e6cae13bdf7ebd9003b0ca82e1d0ff8e1d72..53f4d435e0842fbd0f9c7e35f3edbaf66a6c9a70 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 d7b1f049c8405a0c2719c78d5b00fc3a5c7e7a81..59be7c8ff4a84efec61e432707b54c81ae58c6f5 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 d60b4105e2636b71cd10217b6f48120194d15181..ce5c45adc79b678b83c066b5e3de7bb9344b665e 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 76feddabac165232ad715912e0076249384515fa..b4c6cab2318fc121de2ab4d1f937cf3b66bba6fe 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 48e69443f4e021b765419c658f57b2f95739afc2..773db4bb8531c8d1f9ab7d9fc4e5690c706f1545 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 d9386eeb90a2285538b37aed1dbedebf3d15e5e6..f5df52a5f6325f35d096be2f4f180f1594d1f150 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 5c115bb98430185f64e3fbb8c52d34796b673d9a..5f02f240e0153ee9de68cd59636948566d31eaa9 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 e604916aeed68543a5cb5bef5572ca62b7d13d7a..81491db2e62cb18f16d93600d7fc02b5c547f5ea 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 deb5d335ab965d6709aef20468929ca8d757fb2a..80c625bfc3480aea6cc3a0a2ad0b256ff9e8e1fe 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 3c1edbac8cdeccc685662f1f4bb0f70a4cb928c9..81efbba528f98620f80ba2183eadd218ca06eb4a 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 230c4cb6805e91af2676294e7b71cedb8bcff270..82b13fbe72fc8cc97a46935301c1164a62d57ba5 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 0000000000000000000000000000000000000000..62f21be3b257a39df1b2a491f43321ec74cc1387 --- /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 d1ab275aad9df9c51f59f5ae1747c702565fe4f3..2f01c98d3f9c5b4e7567521b426f9089be14c7e6 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 3aec76e979c3da6f6463c1e7f9fd628b040948bc..7a1214865f312191b70a1ce503a9f9f464640ecf 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 7da8b7aba61f3b45e4ff1b69935e026077aec624..264625a7b62746878fa8652723b4da2e6d416f50 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 3f310a86ea34df112cda566434f7343bb5ce14d7..2de7ce43ea63a1e2451690d2c821be9b62a252c8 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 0ccee36c6e92b398d1120b8bd28cece139cd13b0..df399152aadfe3cbce9a0db85e88d7b80241e711 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 0000000000000000000000000000000000000000..7a9dea36e07a9c267f60c9f380169ddaabda88e5 --- /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 562ee620665e457d916fd6619a20d678258a0b4c..dbdd9d65eb3bdb5ee0bbeb37d0d744d9ce6da2d6 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 6ffe674cc35119cb3ffd1b20f7ade7f4e41bd8fa..b92af3f427fab6d63ab99e01a00704c2b61e8f75 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 39cb1e12d4105bcffde431aa6b90c81002b6e95e..cfc0bbd9629d618e0ec8aacb2315b5f10271f55d 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 Binary files /dev/null and b/tests/test_kernel/ref_u.npy differ 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 Binary files /dev/null and b/tests/test_kernel/ref_u_fft.npy differ diff --git a/tests/test_kernel/ref_v.npy b/tests/test_kernel/ref_v.npy new file mode 100644 index 0000000000000000000000000000000000000000..c854a27d8debf3f664bb65695088dba2ecb41630 Binary files /dev/null and b/tests/test_kernel/ref_v.npy differ 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 Binary files /dev/null and b/tests/test_kernel/ref_v_fft.npy differ diff --git a/tests/test_magcreator.py b/tests/test_magcreator.py index 137b6e670636ee2c825c50e85155fdbafa16a7bc..4abcb950977140dd5bc0db4bb4fd68d2ebfcadc8 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 Binary files /dev/null and b/tests/test_magcreator/ref_ellipse_x.npy differ 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 Binary files /dev/null and b/tests/test_magcreator/ref_ellipse_y.npy differ 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 Binary files /dev/null and b/tests/test_magcreator/ref_ellipse_z.npy differ diff --git a/tests/test_magcreator/ref_ellipsoid.npy b/tests/test_magcreator/ref_ellipsoid.npy new file mode 100644 index 0000000000000000000000000000000000000000..8abca7c48658e9f9df018d7869ba2dc4461b1d9a Binary files /dev/null and b/tests/test_magcreator/ref_ellipsoid.npy differ diff --git a/tests/test_magdata.py b/tests/test_magdata.py index 14d96606f5259af2d4f9909bb1844eecc5f920b2..a178de31891a93609248c941caa5e48032d89499 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 Binary files a/tests/test_magdata/ref_mag_data.nc and b/tests/test_magdata/ref_mag_data.nc differ diff --git a/tests/test_projector.py b/tests/test_projector.py index 6da902fdfb4774031fdc3c1f84039f1dec9d6e39..b328c024452ea24ce745dd8d39c73a39fa04793f 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 Binary files /dev/null and b/tests/test_projector/ref_mag_data.nc differ diff --git a/tests/test_projector/ref_mag_data.txt b/tests/test_projector/ref_mag_data.txt deleted file mode 100644 index 32d452bdcb2800bfdd5e6d46a6261f238d24462a..0000000000000000000000000000000000000000 --- 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_x.nc differ 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_x00.nc differ 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_x45.nc differ 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_x90.nc differ 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_y.nc differ 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_y00.nc differ 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_y45.nc differ 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_y90.nc differ 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 Binary files /dev/null and b/tests/test_projector/ref_mag_proj_z.nc differ diff --git a/tests/test_projector/ref_proj_x.txt b/tests/test_projector/ref_proj_x.txt deleted file mode 100644 index 94ce2a36ef75cec69acef07af19b74d522e1729f..0000000000000000000000000000000000000000 --- 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 b8a14486eb17c76bc28dd0381964ca9e03c0781c..0000000000000000000000000000000000000000 --- 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 eec624bc009a47165dc5410af77e4ac999c388cd..0000000000000000000000000000000000000000 --- 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