From c2d3607a14b60feb19a4ca7b7ad39436876ed8c1 Mon Sep 17 00:00:00 2001
From: Martin Genet <martin.genet@polytechnique.edu>
Date: Fri, 17 May 2019 17:45:04 +0200
Subject: [PATCH] New k-space resampling in generate_images

---
 __init__.py                |  45 +--
 generate_images.py         | 703 ++-----------------------------------
 generate_images_Image.py   | 358 +++++++++++++++++++
 generate_images_Mapping.py | 254 ++++++++++++++
 normalize_images.py        |  95 +++++
 resample_images.py         | 151 ++++++++
 6 files changed, 909 insertions(+), 697 deletions(-)
 create mode 100644 generate_images_Image.py
 create mode 100644 generate_images_Mapping.py
 create mode 100644 normalize_images.py
 create mode 100644 resample_images.py

diff --git a/__init__.py b/__init__.py
index 222a8f7..089a12d 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,31 +1,36 @@
 #this file was generated by __init__.py.py
 
-from compute_displacement_error import *
-from compute_displacements import *
-from compute_quadrature_degree import *
-from compute_strains import *
+from fedic import *
+from plot_binned_strains_vs_radius import *
+from NonlinearSolver import *
+from MeshSeries import *
+from generated_image_expressions_cpp import *
+from plot_strains_vs_radius import *
+from Energy_GeneratedImage import *
 from compute_unwarped_images import *
+from Problem_ImageRegistration import *
+from compute_quadrature_degree import *
+from Energy_Regularization import *
 from compute_warped_images import *
-from compute_warped_mesh import *
+from plot_strains import *
+from image_expressions_cpp import *
+from resample_images import *
 from Energy import *
-from Energy_Regularization import *
+from compute_warped_mesh import *
 from Energy_WarpedImage import *
-from fedic import *
-from fedic2 import *
-from generate_images import *
+from write_VTU_file import *
 from generate_undersampled_images import *
-from generated_image_expressions_cpp import *
-from image_expressions_cpp import *
+from normalize_images import *
+from plot_regional_strains import *
 from image_expressions_py import *
 from ImageIterator import *
-from ImageSeries import *
-from MeshSeries import *
-from NonlinearSolver import *
-from plot_binned_strains_vs_radius import *
+from fedic2 import *
+from generated_grad_expressions_cpp import *
 from plot_displacement_error import *
-from plot_regional_strains import *
-from plot_strains import *
-from plot_strains_vs_radius import *
 from Problem import *
-from Problem_ImageRegistration import *
-from write_VTU_file import *
+from compute_displacements import *
+from generate_images import *
+from ImageSeries import *
+from compute_displacement_error import *
+from project import *
+from compute_strains import *
diff --git a/generate_images.py b/generate_images.py
index c0c28f1..63ebfa1 100644
--- a/generate_images.py
+++ b/generate_images.py
@@ -19,640 +19,8 @@ import myPythonLibrary as mypy
 import myVTKPythonLibrary as myvtk
 
 import dolfin_dic as ddic
-
-################################################################################
-
-#class ImagesInfo():
-    #def __init__(self, n_dim, L, n_voxels, n_integration, T, n_frames, data_type, images_folder, images_basename):
-        #assert (n_dim in (1,2,3))
-        #self.n_dim = n_dim
-
-        #if (type(L) == float):
-            #assert (L>0)
-            #self.L = numpy.array([L]*self.n_dim)
-        #elif (type(L) == int):
-            #assert (L>0)
-            #self.L = numpy.array([float(L)]*self.n_dim)
-        #else:
-            #assert (len(L) == self.n_dim)
-            #self.L = numpy.array(L)
-            #assert ((self.L>0).all())
-
-        #if (type(n_voxels) == int):
-            #assert (n_voxels>0)
-            #self.n_voxels = numpy.array([n_voxels]*self.n_dim)
-        #else:
-            #assert (len(n_voxels) == self.n_dim)
-            #self.n_voxels = numpy.array(n_voxels)
-            #assert ((self.n_voxels>0).all())
-
-        #if (type(n_integration) == int):
-            #assert (n_integration>0)
-            #self.n_integration = numpy.array([n_integration]*self.n_dim)
-        #else:
-            #assert (len(n_integration) == self.n_dim)
-            #self.n_integration = numpy.array(n_integration)
-            #assert ((self.n_integration>0).all())
-
-        #assert (T>0.)
-        #self.T = T
-
-        #assert (n_frames>0)
-        #self.n_frames = n_frames
-
-        #assert (data_type in ("int", "float", "unsigned char", "unsigned short", "unsigned int", "unsigned long", "unsigned float" "uint8", "uint16", "uint32", "uint64", "ufloat"))
-        #self.data_type = data_type
-
-        #self.images_folder = images_folder
-        #self.images_basename = images_basename
-
-#class StructureInfo():
-    #def __init__(self, images, type, **kwargs):
-        #assert (type in ("no", "heart"))
-        #self["type"] = type
-        #if (self["type"] == "heart"):
-            #self.Ri = kwargs["Ri"]
-            #self.Re = kwargs["Re"]
-            #if (images.n_dim == 3):
-                #self.Zmin = kwargs["Zmin"] if ("Zmin" in kwargs.keys()) else 0.
-                #self.Zmax = kwargs["Zmax"] if ("Zmax" in kwargs.keys()) else images.L[2]
-
-#class TextureInfo():
-    #def __init__(self, type, **kwargs):
-        #assert (type in ("no", "taggX", "taggY", "taggZ"))
-        #self["type"] = type
-
-#class NoiseInfo():
-    #def __init__(self, type, **kwargs):
-        #self["type"] = type
-
-#class DeformationInfo():
-    #def __init__(self, type, **kwargs):
-        #self["type"] = type
-
-#class EvolutionInfo():
-    #def __init__(self, type, **kwargs):
-        #self["type"] = type
-
-################################################################################
-
-class Image():
-
-    def __init__(self, images, structure, texture, noise, generate_image_gradient=False):
-
-        self.L = images["L"]
-
-        # structure
-        if (structure["type"] == "no"):
-            self.I0_structure = self.I0_structure_no_wGrad if (generate_image_gradient) else self.I0_structure_no
-        elif (structure["type"] == "box"):
-            self.I0_structure = self.I0_structure_box_wGrad if (generate_image_gradient) else self.I0_structure_box
-            self.Xmin = structure["Xmin"]
-            self.Xmax = structure["Xmax"]
-        elif (structure["type"] == "heart"):
-            if (images["n_dim"] == 2):
-                self.I0_structure = self.I0_structure_heart_2_wGrad if (generate_image_gradient) else self.I0_structure_heart_2
-                self.R = float()
-                self.Ri = structure["Ri"]
-                self.Re = structure["Re"]
-            elif (images["n_dim"] == 3):
-                self.I0_structure = self.I0_structure_heart_3_wGrad if (generate_image_gradient) else self.I0_structure_heart_3
-                self.R = float()
-                self.Ri = structure["Ri"]
-                self.Re = structure["Re"]
-                self.Zmin = structure.Zmin if ("Zmin" in structure) else 0.
-                self.Zmax = structure.Zmax if ("Zmax" in structure) else images["L"][2]
-            else:
-                assert (0), "n_dim must be \"2\" or \"3 for \"heart\" type structure. Aborting."
-        else:
-            assert (0), "structure type must be \"no\", \"box\" or \"heart\". Aborting."
-
-        # texture
-        if (texture["type"] == "no"):
-            self.I0_texture = self.I0_texture_no_wGrad if (generate_image_gradient) else self.I0_texture_no
-        elif (texture["type"].startswith("tagging")):
-            if   (images["n_dim"] == 1):
-                if ("-signed" in texture["type"]):
-                    self.I0_texture = self.I0_texture_tagging_signed_X_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_X
-                else:
-                    self.I0_texture = self.I0_texture_tagging_X_wGrad if (generate_image_gradient) else self.I0_texture_tagging_X
-            elif (images["n_dim"] == 2):
-                if ("-signed" in texture["type"]):
-                    if   ("-addComb" in texture["type"]):
-                        self.I0_texture = self.I0_texture_tagging_signed_XY_wAdditiveCombination_wGrad       if (generate_image_gradient) else self.I0_texture_tagging_signed_XY_wAdditiveCombination
-                    elif ("-diffComb" in texture["type"]):
-                        self.I0_texture = self.I0_texture_tagging_signed_XY_wDifferentiableCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_XY_wDifferentiableCombination
-                    else:
-                        self.I0_texture = self.I0_texture_tagging_signed_XY_wMultiplicativeCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_XY_wMultiplicativeCombination
-                else:
-                    if   ("-addComb" in texture["type"]):
-                        self.I0_texture = self.I0_texture_tagging_XY_wAdditiveCombination_wGrad       if (generate_image_gradient) else self.I0_texture_tagging_XY_wAdditiveCombination
-                    elif ("-diffComb" in texture["type"]):
-                        self.I0_texture = self.I0_texture_tagging_XY_wDifferentiableCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_XY_wDifferentiableCombination
-                    else:
-                        self.I0_texture = self.I0_texture_tagging_XY_wMultiplicativeCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_XY_wMultiplicativeCombination
-            elif (images["n_dim"] == 3):
-                if ("-signed" in texture["type"]):
-                    if   ("-addComb" in texture["type"]):
-                        self.I0_texture = self.I0_texture_tagging_signed_XYZ_wAdditiveCombination_wGrad       if (generate_image_gradient) else self.I0_texture_tagging_signed_XYZ_wAdditiveCombination
-                    elif ("-diffComb" in texture["type"]):
-                        self.I0_texture = self.I0_texture_tagging_signed_XYZ_wDifferentiableCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_XYZ_wDifferentiableCombination
-                    else:
-                        self.I0_texture = self.I0_texture_tagging_signed_XYZ_wMultiplicativeCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_XYZ_wMultiplicativeCombination
-                else:
-                    if   ("-addComb" in texture["type"]):
-                        self.I0_texture = self.I0_texture_tagging_XYZ_wAdditiveCombination_wGrad       if (generate_image_gradient) else self.I0_texture_tagging_XYZ_wAdditiveCombination
-                    elif ("-diffComb" in texture["type"]):
-                        self.I0_texture = self.I0_texture_tagging_XYZ_wDifferentiableCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_XYZ_wDifferentiableCombination
-                    else:
-                        self.I0_texture = self.I0_texture_tagging_XYZ_wMultiplicativeCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_XYZ_wMultiplicativeCombination
-            else:
-                assert (0), "n_dim must be \"1\", \"2\" or \"3\". Aborting."
-            self.s = texture["s"]
-        elif (texture["type"].startswith("taggX")):
-            if ("-signed" in texture["type"]):
-                self.I0_texture = self.I0_texture_tagging_signed_X_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_X
-            else:
-                self.I0_texture = self.I0_texture_tagging_X_wGrad if (generate_image_gradient) else self.I0_texture_tagging_X
-            self.s = texture["s"]
-        elif (texture["type"].startswith("taggY")):
-            if ("-signed" in texture["type"]):
-                self.I0_texture = self.I0_texture_tagging_signed_Y_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_Y
-            else:
-                self.I0_texture = self.I0_texture_tagging_Y_wGrad if (generate_image_gradient) else self.I0_texture_tagging_Y
-            self.s = texture["s"]
-        elif (texture["type"].startswith("taggZ")):
-            if ("-signed" in texture["type"]):
-                self.I0_texture = self.I0_texture_tagging_signed_Z_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_Z
-            else:
-                self.I0_texture = self.I0_texture_tagging_Z_wGrad if (generate_image_gradient) else self.I0_texture_tagging_Z
-            self.s = texture["s"]
-        else:
-            assert (0), "texture type must be \"no\", \"tagging\", \"taggX\", \"taggY\" or \"taggZ\". Aborting."
-
-        # noise
-        if (noise["type"] == "no"):
-            self.I0_noise = self.I0_noise_no_wGrad if (generate_image_gradient) else self.I0_noise_no
-        elif (noise["type"] == "normal"):
-            self.I0_noise = self.I0_noise_normal_wGrad if (generate_image_gradient) else self.I0_noise_normal
-            self.avg = noise["avg"] if ("avg" in noise) else 0.
-            self.std = noise["stdev"]
-        else:
-            assert (0), "noise type must be \"no\" or \"normal\". Aborting."
-
-    def I0(self, X, I):
-        self.I0_structure(X, I)
-        self.I0_texture(X, I)
-        self.I0_noise(I)
-
-    def I0_wGrad(self, X, I, G):
-        self.I0_structure_wGrad(X, I, G)
-        self.I0_texture_wGrad(X, I, G)
-        self.I0_noise_wGrad(I, G)
-
-    def I0_structure_no(self, X, I):
-        I[0] = 1.
-
-    def I0_structure_no_wGrad(self, X, I, G):
-        self.I0_structure_no(X, I)
-        G[:] = 1. # MG 20180806: gradient is given by texture; here it is just indicator function
-
-    def I0_structure_box(self, X, I):
-        if all(numpy.greater_equal(X, self.Xmin)) and all(numpy.less_equal(X, self.Xmax)):
-            I[0] = 1.
-        else:
-            I[0] = 0.
-
-    def I0_structure_box_wGrad(self, X, I, G):
-        if all(numpy.greater_equal(X, self.Xmin)) and all(numpy.less_equal(X, self.Xmax)):
-            I[0] = 1.
-            G[:] = 1. # MG 20180806: gradient is given by texture; here it is just indicator function
-        else:
-            I[0] = 0.
-            G[:] = 0. # MG 20180806: gradient is given by texture; here it is just indicator function
-
-    def I0_structure_heart_2(self, X, I):
-        self.R = ((X[0]-self.L[0]/2)**2 + (X[1]-self.L[1]/2)**2)**(1./2)
-        if (self.R >= self.Ri) and (self.R <= self.Re):
-            I[0] = 1.
-        else:
-            I[0] = 0.
-
-    def I0_structure_heart_2_wGrad(self, X, I, G):
-        self.R = ((X[0]-self.L[0]/2)**2 + (X[1]-self.L[1]/2)**2)**(1./2)
-        if (self.R >= self.Ri) and (self.R <= self.Re):
-            I[0] = 1.
-            G[:] = 1. # MG 20180806: gradient is given by texture; here it is just indicator function
-        else:
-            I[0] = 0.
-            G[:] = 0. # MG 20180806: gradient is given by texture; here it is just indicator function
-
-    def I0_structure_heart_3(self, X, I):
-        self.R = ((X[0]-self.L[0]/2)**2 + (X[1]-self.L[1]/2)**2)**(1./2)
-        if (self.R >= self.Ri) and (self.R <= self.Re) and (X[2] >= self.Zmin) and (X[2] <= self.Zmax):
-            I[0] = 1.
-        else:
-            I[0] = 0.
-
-    def I0_structure_heart_3_wGrad(self, X, I, G):
-        self.R = ((X[0]-self.L[0]/2)**2 + (X[1]-self.L[1]/2)**2)**(1./2)
-        if (self.R >= self.Ri) and (self.R <= self.Re) and (X[2] >= self.Zmin) and (X[2] <= self.Zmax):
-            I[0] = 1.
-            G[:] = 1. # MG 20180806: gradient is given by texture; here it is just indicator function
-        else:
-            I[0] = 0.
-            G[:] = 0. # MG 20180806: gradient is given by texture; here it is just indicator function
-
-    def I0_texture_no(self, X, I):
-        I[0] *= 1.
-
-    def I0_texture_no_wGrad(self, X, I, G):
-        self.I0_texture_no(X, I)
-        G[:] *= 0.
-
-    def I0_texture_tagging_X(self, X, I):
-        I[0] *= abs(math.sin(math.pi*X[0]/self.s))
-
-    def I0_texture_tagging_X_wGrad(self, X, I, G):
-        self.I0_texture_tagging_X(X, I)
-        G[0] *= math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s)
-        G[1] *= 0.
-        G[2] *= 0.
-
-    def I0_texture_tagging_Y(self, X, I):
-        I[0] *= abs(math.sin(math.pi*X[1]/self.s))
-
-    def I0_texture_tagging_Y_wGrad(self, X, I, G):
-        self.I0_texture_tagging_Y(X, I)
-        G[0] *= 0.
-        G[1] *= math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s)
-        G[2] *= 0.
-
-    def I0_texture_tagging_Z(self, X, I):
-        I[0] *= abs(math.sin(math.pi*X[2]/self.s))
-
-    def I0_texture_tagging_Z_wGrad(self, X, I, G):
-        self.I0_texture_tagging_Z(X, I)
-        G[0] *= 0.
-        G[1] *= 0.
-        G[2] *= math.copysign(1, math.sin(math.pi*X[2]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[2]/self.s)
-
-    def I0_texture_tagging_XY_wDifferentiableCombination(self, X, I):
-        I[0] *= (1 + 3 * abs(math.sin(math.pi*X[0]/self.s))
-                       * abs(math.sin(math.pi*X[1]/self.s)))**(1./2) - 1
-
-    def I0_texture_tagging_XY_wDifferentiableCombination_wGrad(self, X, I, G):
-        self.I0_texture_tagging_XY_wDifferentiableCombination(X, I)
-        G[0] *= 3 * math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) * abs(math.sin(math.pi*X[1]/self.s)) / 2 / (I[0] + 1)
-        G[1] *= 3 * math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) / 2 / (I[0] + 1)
-        G[2] *= 0.
-
-    def I0_texture_tagging_XY_wMultiplicativeCombination(self, X, I):
-        I[0] *= (abs(math.sin(math.pi*X[0]/self.s))
-             *   abs(math.sin(math.pi*X[1]/self.s)))**(1./2)
-
-    def I0_texture_tagging_XY_wMultiplicativeCombination_wGrad(self, X, I, G):
-        self.I0_texture_tagging_XY_wMultiplicativeCombination(X, I)
-        G[0] *= math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) * abs(math.sin(math.pi*X[1]/self.s)) / 2 / I[0]
-        G[1] *= math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) / 2 / I[0]
-        G[2] *= 0.
-
-    def I0_texture_tagging_XYZ_wDifferentiableCombination(self, X, I):
-        I[0] *= (1 + 7 * abs(math.sin(math.pi*X[0]/self.s))
-                       * abs(math.sin(math.pi*X[1]/self.s))
-                       * abs(math.sin(math.pi*X[2]/self.s)))**(1./3) - 1
-
-    def I0_texture_tagging_XYZ_wDifferentiableCombination_wGrad(self, X, I, G):
-        self.I0_texture_tagging_XYZ_wDifferentiableCombination(X, I)
-        G[0] *= 7 * math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) * abs(math.sin(math.pi*X[1]/self.s)) * abs(math.sin(math.pi*X[2]/self.s)) / 3 / (I[0] + 1)
-        G[1] *= 7 * math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) * abs(math.sin(math.pi*X[2]/self.s)) / 3 / (I[0] + 1)
-        G[2] *= 7 * math.copysign(1, math.sin(math.pi*X[2]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[2]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) * abs(math.sin(math.pi*X[1]/self.s)) / 3 / (I[0] + 1)
-
-    def I0_texture_tagging_XYZ_wMultiplicativeCombination(self, X, I):
-        I[0] *= (abs(math.sin(math.pi*X[0]/self.s))
-             *   abs(math.sin(math.pi*X[1]/self.s))
-             *   abs(math.sin(math.pi*X[2]/self.s)))**(1./3)
-
-    def I0_texture_tagging_XYZ_wMultiplicativeCombination_wGrad(self, X, I, G):
-        self.I0_texture_tagging_XYZ_wMultiplicativeCombination(X, I)
-        G[0] *= math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) * abs(math.sin(math.pi*X[1]/self.s)) * abs(math.sin(math.pi*X[2]/self.s)) / 3 / I[0]**2
-        G[1] *= math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) * abs(math.sin(math.pi*X[2]/self.s)) / 3 / I[0]**2
-        G[2] *= math.copysign(1, math.sin(math.pi*X[2]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[2]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) * abs(math.sin(math.pi*X[1]/self.s)) / 3 / I[0]**2
-
-    def I0_texture_tagging_signed_X(self, X, I):
-        I[0] *= (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
-
-    def I0_texture_tagging_signed_X_wGrad(self, X, I, G):
-        self.I0_texture_tagging_signed_X(X, I)
-        G[0] *= (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2) / 2
-        G[1] *= 0.
-        G[2] *= 0.
-
-    def I0_texture_tagging_signed_Y(self, X, I):
-        I[0] *= (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2
-
-    def I0_texture_tagging_signed_Y_wGrad(self, X, I, G):
-        self.I0_texture_tagging_signed_Y(X, I)
-        G[0] *= 0.
-        G[1] *= (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2) / 2
-        G[2] *= 0.
-
-    def I0_texture_tagging_signed_Z(self, X, I):
-        I[0] *= (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2
-
-    def I0_texture_tagging_signed_Z_wGrad(self, X, I, G):
-        self.I0_texture_tagging_signed_Z(X, I)
-        G[0] *= 0.
-        G[1] *= 0.
-        G[2] *= (math.pi/self.s) * math.cos(math.pi*X[2]/self.s-math.pi/2) / 2
-
-    def I0_texture_tagging_signed_XY_wDifferentiableCombination(self, X, I):
-        I[0] *= (1 + 3 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
-                       * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2)**(1./2) - 1
-
-    def I0_texture_tagging_signed_XY_wDifferentiableCombination_wGrad(self, X, I, G):
-        self.I0_texture_tagging_signed_XY_wDifferentiableCombination(X, I)
-        G[0] *= 3 * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2 / 2 / (I[0] + 1)
-        G[1] *= 3 * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2 / 2 / (I[0] + 1)
-        G[2] *= 0.
-
-    def I0_texture_tagging_signed_XY_wAdditiveCombination(self, X, I):
-        I[0] *= ((1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
-              +  (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2) / 2
-
-    def I0_texture_tagging_signed_XY_wAdditiveCombination_wGrad(self, X, I, G):
-        self.I0_texture_tagging_signed_XY_wAdditiveCombination(X, I)
-        G[0] *= (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2)/2 / 2
-        G[1] *= (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2)/2 / 2
-        G[2] *= 0.
-
-    def I0_texture_tagging_signed_XYZ_wDifferentiableCombination(self, X, I):
-        I[0] *= (1 + 7 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
-                       * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2
-                       * (1+math.sin(math.pi*X[2]/self.s-math.pi/2))/2)**(1./3) - 1
-
-    def I0_texture_tagging_signed_XYZ_wDifferentiableCombination_wGrad(self, X, I, G):
-        self.I0_texture_tagging_signed_XYZ_wDifferentiableCombination(X, I)
-        G[0] *= 7 * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2 * (1+math.sin(math.pi*X[2]/self.s-math.pi/2))/2 / 3 / (I[0] + 1)
-        G[1] *= 7 * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2 * (1+math.sin(math.pi*X[2]/self.s-math.pi/2))/2 / 3 / (I[0] + 1)
-        G[2] *= 7 * (math.pi/self.s) * math.cos(math.pi*X[2]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2 * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2 / 3 / (I[0] + 1)
-
-    def I0_texture_tagging_signed_XYZ_wAdditiveCombination(self, X, I):
-        I[0] *= ((1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
-              +  (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2
-              +  (1+math.sin(math.pi*X[2]/self.s-math.pi/2))/2) / 3
-
-    def I0_texture_tagging_signed_XYZ_wAdditiveCombination_wGrad(self, X, I, G):
-        self.I0_texture_tagging_signed_XYZ_wAdditiveCombination(X, I)
-        G[0] *= (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2)/2 / 3
-        G[1] *= (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2)/2 / 3
-        G[2] *= (math.pi/self.s) * math.cos(math.pi*X[2]/self.s-math.pi/2)/2 / 3
-
-    def I0_noise_no(self, I):
-        pass
-
-    def I0_noise_no_wGrad(self, I, G):
-        pass
-
-    def I0_noise_normal(self, I):
-        I[0] += random.normalvariate(self.avg, self.std)
-
-    def I0_noise_normal_wGrad(self, I, G):
-        self.I0_noise_normal(I)
-        G[:] += [2*random.normalvariate(self.avg, self.std) for k in xrange(len(G))]
-
-################################################################################
-
-class Mapping:
-
-    def __init__(self, images, structure, deformation, evolution, generate_image_gradient):
-
-        self.deformation = deformation
-        if (self.deformation["type"] == "no"):
-            self.init_t = self.init_t_no
-            self.X = self.X_no
-            self.x = self.x_no
-        elif (self.deformation["type"] == "translation"):
-            self.init_t = self.init_t_translation
-            self.X = self.X_translation
-            self.x = self.x_translation
-            self.D = numpy.empty(3)
-        elif (self.deformation["type"] == "rotation"):
-            self.init_t = self.init_t_rotation
-            self.X = self.X_rotation
-            self.x = self.x_rotation
-            self.C = numpy.empty(3)
-            self.R = numpy.empty((3,3))
-            self.Rinv = numpy.empty((3,3))
-        elif (self.deformation["type"] == "homogeneous"):
-            self.init_t = self.init_t_homogeneous
-            self.X = self.X_homogeneous
-            self.x = self.x_homogeneous
-        elif (self.deformation["type"] == "heart"):
-            assert (structure["type"] == "heart"), "structure type must be \"heart\" for \"heart\" type deformation. Aborting."
-            self.init_t = self.init_t_heart
-            self.X = self.X_heart
-            self.x = self.x_heart
-            self.x_inplane = numpy.empty(2)
-            self.X_inplane = numpy.empty(2)
-            self.rt = numpy.empty(2)
-            self.RT = numpy.empty(2)
-            self.L = images["L"]
-            self.Ri = structure["Ri"]
-            self.Re = structure["Re"]
-            self.R = numpy.empty((3,3))
-        else:
-            assert (0), "deformation type must be \"no\", \"translation\", \"rotation\", \"homogeneous\" or \"heart\". Aborting."
-
-        if (evolution["type"] == "linear"):
-            self.phi = self.phi_linear
-        elif (evolution["type"] == "sine"):
-            self.phi = self.phi_sine
-            self.T = evolution["T"]
-        else:
-            assert (0), "evolution ("+evolution["type"]+") type must be \"linear\" or \"sine\". Aborting."
-
-    def phi_linear(self, t):
-        return t
-
-    def phi_sine(self, t):
-        return math.sin(math.pi*t/self.T)**2
-
-    def init_t_no(self, t):
-        pass
-
-    def init_t_translation(self, t):
-        self.D[0] = self.deformation["Dx"] if ("Dx" in self.deformation) else 0.
-        self.D[1] = self.deformation["Dy"] if ("Dy" in self.deformation) else 0.
-        self.D[2] = self.deformation["Dz"] if ("Dz" in self.deformation) else 0.
-        self.D *= self.phi(t)
-
-    def init_t_rotation(self, t):
-        self.C[0] = self.deformation["Cx"] if ("Cx" in self.deformation) else 0.
-        self.C[1] = self.deformation["Cy"] if ("Cy" in self.deformation) else 0.
-        self.C[2] = self.deformation["Cz"] if ("Cz" in self.deformation) else 0.
-        Rx = self.deformation["Rx"]*math.pi/180*self.phi(t) if ("Rx" in self.deformation) else 0.
-        Ry = self.deformation["Ry"]*math.pi/180*self.phi(t) if ("Ry" in self.deformation) else 0.
-        Rz = self.deformation["Rz"]*math.pi/180*self.phi(t) if ("Rz" in self.deformation) else 0.
-        RRx = numpy.array([[          1. ,           0. ,           0. ],
-                           [          0. , +math.cos(Rx), -math.sin(Rx)],
-                           [          0. , +math.sin(Rx), +math.cos(Rx)]])
-        RRy = numpy.array([[+math.cos(Ry),           0. , +math.sin(Ry)],
-                           [          0. ,           1. ,           0. ],
-                           [-math.sin(Ry),           0. , +math.cos(Ry)]])
-        RRz = numpy.array([[+math.cos(Rz), -math.sin(Rz),           0. ],
-                           [+math.sin(Rz), +math.cos(Rz),           0. ],
-                           [          0. ,           0. ,           1. ]])
-        self.R[:,:] = numpy.dot(numpy.dot(RRx, RRy), RRz)
-        self.Rinv[:,:] = numpy.linalg.inv(self.R)
-
-    def init_t_homogeneous(self, t):
-        if (any(E in self.deformation for E in ("Exx", "Eyy", "Ezz"))): # build F from E
-            Exx = self.deformation["Exx"] if ("Exx" in self.deformation) else 0.
-            Eyy = self.deformation["Eyy"] if ("Eyy" in self.deformation) else 0.
-            Ezz = self.deformation["Ezz"] if ("Ezz" in self.deformation) else 0.
-            self.F = numpy.array([[Exx,  0.,  0.],
-                                  [ 0., Eyy,  0.],
-                                  [ 0.,  0., Ezz]])*self.phi(t)
-            self.F *= 2
-            self.F += numpy.eye(3)
-            w, v = numpy.linalg.eig(self.F)
-            # assert (numpy.diag(numpy.dot(numpy.dot(numpy.transpose(v), self.F), v)) == w).all(), str(numpy.dot(numpy.dot(numpy.transpose(v), self.F), v))+" ≠ "+str(numpy.diag(w))+". Aborting."
-            self.F = numpy.dot(numpy.dot(v, numpy.diag(numpy.sqrt(w))), numpy.transpose(v))
-        else:
-            Fxx = self.deformation["Fxx"] if ("Fxx" in self.deformation) else 0.
-            Fyy = self.deformation["Fyy"] if ("Fyy" in self.deformation) else 0.
-            Fzz = self.deformation["Fzz"] if ("Fzz" in self.deformation) else 0.
-            Fxy = self.deformation["Fxy"] if ("Fxy" in self.deformation) else 0.
-            Fyx = self.deformation["Fyx"] if ("Fyx" in self.deformation) else 0.
-            Fyz = self.deformation["Fyz"] if ("Fyz" in self.deformation) else 0.
-            Fzy = self.deformation["Fzy"] if ("Fzy" in self.deformation) else 0.
-            Fzx = self.deformation["Fzx"] if ("Fzx" in self.deformation) else 0.
-            Fxz = self.deformation["Fxz"] if ("Fxz" in self.deformation) else 0.
-            self.F = numpy.eye(3) + (numpy.array([[Fxx, Fxy, Fxz],
-                                                  [Fyx, Fyy, Fyz],
-                                                  [Fzx, Fzy, Fzz]])-numpy.eye(3))*self.phi(t)
-        self.Finv = numpy.linalg.inv(self.F)
-
-    def init_t_heart(self, t):
-        self.dRi = self.deformation["dRi"]*self.phi(t) if ("dRi" in self.deformation) else 0.
-        self.dRe = self.deformation["dRi"]*self.phi(t) if ("dRi" in self.deformation) else 0.
-        self.dTi = self.deformation["dTi"]*self.phi(t) if ("dTi" in self.deformation) else 0.
-        self.dTe = self.deformation["dTe"]*self.phi(t) if ("dTe" in self.deformation) else 0.
-        self.A = numpy.array([[1.-(self.dRi-self.dRe)/(self.Re-self.Ri), 0.],
-                              [  -(self.dTi-self.dTe)/(self.Re-self.Ri), 1.]])
-        self.Ainv = numpy.linalg.inv(self.A)
-        self.B = numpy.array([(1.+self.Ri/(self.Re-self.Ri))*self.dRi-self.Ri/(self.Re-self.Ri)*self.dRe,
-                              (1.+self.Ri/(self.Re-self.Ri))*self.dTi-self.Ri/(self.Re-self.Ri)*self.dTe])
-
-    def X_no(self, x, X, Finv=None):
-        X[:] = x
-        if (Finv is not None): Finv[:,:] = numpy.identity(numpy.sqrt(numpy.size(Finv)))
-
-    def X_translation(self, x, X, Finv=None):
-        X[:] = x - self.D
-        if (Finv is not None): Finv[:,:] = numpy.identity(numpy.sqrt(numpy.size(Finv)))
-
-    def X_rotation(self, x, X, Finv=None):
-        X[:] = numpy.dot(self.Rinv, x - self.C) + self.C
-        if (Finv is not None): Finv[:,:] = self.Rinv
-
-    def X_homogeneous(self, x, X, Finv=None):
-        X[:] = numpy.dot(self.Finv, x)
-        if (Finv is not None): Finv[:,:] = self.Finv
-
-    def X_heart(self, x, X, Finv=None):
-        #print "x = "+str(x)
-        self.x_inplane[0] = x[0] - self.L[0]/2
-        self.x_inplane[1] = x[1] - self.L[1]/2
-        #print "x_inplane = "+str(self.x_inplane)
-        self.rt[0] = numpy.linalg.norm(self.x_inplane)
-        self.rt[1] = math.atan2(self.x_inplane[1], self.x_inplane[0])
-        #print "rt = "+str(self.rt)
-        self.RT[:] = numpy.dot(self.Ainv, self.rt-self.B)
-        #print "RT = "+str(self.RT)
-        X[0] = self.RT[0] * math.cos(self.RT[1]) + self.L[0]/2
-        X[1] = self.RT[0] * math.sin(self.RT[1]) + self.L[1]/2
-        X[2] = x[2]
-        #print "X = "+str(X)
-        if (Finv is not None):
-            Finv[0,0] = 1.+(self.dRe-self.dRi)/(self.Re-self.Ri)
-            Finv[0,1] = 0.
-            Finv[0,2] = 0.
-            Finv[1,0] = (self.dTe-self.dTi)/(self.Re-self.Ri)*self.rt[0]
-            Finv[1,1] = self.rt[0]/self.RT[0]
-            Finv[1,2] = 0.
-            Finv[2,0] = 0.
-            Finv[2,1] = 0.
-            Finv[2,2] = 1.
-            #print "F = "+str(Finv)
-            Finv[:,:] = numpy.linalg.inv(Finv)
-            #print "Finv = "+str(Finv)
-            self.R[0,0] = +math.cos(self.RT[1])
-            self.R[0,1] = +math.sin(self.RT[1])
-            self.R[0,2] = 0.
-            self.R[1,0] = -math.sin(self.RT[1])
-            self.R[1,1] = +math.cos(self.RT[1])
-            self.R[1,2] = 0.
-            self.R[2,0] = 0.
-            self.R[2,1] = 0.
-            self.R[2,2] = 1.
-            #print "R = "+str(self.R)
-            Finv[:] = numpy.dot(numpy.transpose(self.R), numpy.dot(Finv, self.R))
-            #print "Finv = "+str(Finv)
-
-    def x_no(self, X, x, F=None):
-        x[:] = X
-        if (F is not None): F[:,:] = numpy.identity(numpy.sqrt(numpy.size(F)))
-
-    def x_translation(self, X, x, F=None):
-        x[:] = X + self.D
-        if (F is not None): F[:,:] = numpy.identity(numpy.sqrt(numpy.size(F)))
-
-    def x_rotation(self, X, x, F=None):
-        x[:] = numpy.dot(self.R, X - self.C) + self.C
-        if (F is not None): F[:,:] = self.R
-
-    def x_homogeneous(self, X, x, F=None):
-        x[:] = numpy.dot(self.F, X)
-        if (F is not None): F[:,:] = self.F
-
-    def x_heart(self, X, x, F=None):
-        #print "X = "+str(X)
-        self.X_inplane[0] = X[0] - self.L[0]/2
-        self.X_inplane[1] = X[1] - self.L[1]/2
-        #print "X_inplane = "+str(self.X_inplane)
-        self.RT[0] = numpy.linalg.norm(self.X_inplane)
-        self.RT[1] = math.atan2(self.X_inplane[1], self.X_inplane[0])
-        #print "RT = "+str(self.RT)
-        self.rt[:] = numpy.dot(self.A, self.RT) + self.B
-        #print "rt = "+str(self.rt)
-        x[0] = self.rt[0] * math.cos(self.rt[1]) + self.L[0]/2
-        x[1] = self.rt[0] * math.sin(self.rt[1]) + self.L[1]/2
-        x[2] = X[2]
-        #print "x = "+str(x)
-        if (F is not None):
-            F[0,0] = 1.+(self.dRe-self.dRi)/(self.Re-self.Ri)
-            F[0,1] = 0.
-            F[0,2] = 0.
-            F[1,0] = (self.dTe-self.dTi)/(self.Re-self.Ri)*self.rt[0]
-            F[1,1] = self.rt[0]/self.RT[0]
-            F[1,2] = 0.
-            F[2,0] = 0.
-            F[2,1] = 0.
-            F[2,2] = 1.
-            #print "F = "+str(F)
-            self.R[0,0] = +math.cos(self.RT[1])
-            self.R[0,1] = +math.sin(self.RT[1])
-            self.R[0,2] = 0.
-            self.R[1,0] = -math.sin(self.RT[1])
-            self.R[1,1] = +math.cos(self.RT[1])
-            self.R[1,2] = 0.
-            self.R[2,0] = 0.
-            self.R[2,1] = 0.
-            self.R[2,2] = 1.
-            F[:] = numpy.dot(numpy.transpose(self.R), numpy.dot(F, self.R))
-            #print "F = "+str(F)
+from generate_images_Image import Image
+from generate_images_Mapping import Mapping
 
 ################################################################################
 
@@ -699,8 +67,8 @@ def generateImages(
     assert ("n_integration" not in images),\
         "\"n_integration\" has been deprecated. Use \"resampling\" instead. Aborting."
 
-    if ("resampling" not in images):
-        images["resampling"] = [1]*images["n_dim"]
+    if ("resampling_factors" not in images):
+        images["resampling_factors"] = [1]*images["n_dim"]
     if ("zfill" not in images):
         images["zfill"] = len(str(images["n_frames"]))
     if ("ext" not in images):
@@ -725,20 +93,20 @@ def generateImages(
     vtk_image = vtk.vtkImageData()
 
     if   (images["n_dim"] == 1):
-        vtk_image.SetExtent([0, images["n_voxels"][0]*images["resampling"][0]-1, 0,                                               0, 0,                                               0])
+        vtk_image.SetExtent([0, images["n_voxels"][0]*images["resampling_factors"][0]-1, 0,                                               0, 0,                                               0])
     elif (images["n_dim"] == 2):
-        vtk_image.SetExtent([0, images["n_voxels"][0]*images["resampling"][0]-1, 0, images["n_voxels"][1]*images["resampling"][1]-1, 0,                                               0])
+        vtk_image.SetExtent([0, images["n_voxels"][0]*images["resampling_factors"][0]-1, 0, images["n_voxels"][1]*images["resampling_factors"][1]-1, 0,                                               0])
     elif (images["n_dim"] == 3):
-        vtk_image.SetExtent([0, images["n_voxels"][0]*images["resampling"][0]-1, 0, images["n_voxels"][1]*images["resampling"][1]-1, 0, images["n_voxels"][2]*images["resampling"][2]-1])
+        vtk_image.SetExtent([0, images["n_voxels"][0]*images["resampling_factors"][0]-1, 0, images["n_voxels"][1]*images["resampling_factors"][1]-1, 0, images["n_voxels"][2]*images["resampling_factors"][2]-1])
     else:
         assert (0), "n_dim must be \"1\", \"2\" or \"3\". Aborting."
 
     if   (images["n_dim"] == 1):
-        spacing = numpy.array([images["L"][0]/images["n_voxels"][0]/images["resampling"][0],                                                           1.,                                                           1.])
+        spacing = numpy.array([images["L"][0]/images["n_voxels"][0]/images["resampling_factors"][0],                                                           1.,                                                           1.])
     elif (images["n_dim"] == 2):
-        spacing = numpy.array([images["L"][0]/images["n_voxels"][0]/images["resampling"][0], images["L"][1]/images["n_voxels"][1]/images["resampling"][1],                                                           1.])
+        spacing = numpy.array([images["L"][0]/images["n_voxels"][0]/images["resampling_factors"][0], images["L"][1]/images["n_voxels"][1]/images["resampling_factors"][1],                                                           1.])
     elif (images["n_dim"] == 3):
-        spacing = numpy.array([images["L"][0]/images["n_voxels"][0]/images["resampling"][0], images["L"][1]/images["n_voxels"][1]/images["resampling"][1], images["L"][2]/images["n_voxels"][2]/images["resampling"][2]])
+        spacing = numpy.array([images["L"][0]/images["n_voxels"][0]/images["resampling_factors"][0], images["L"][1]/images["n_voxels"][1]/images["resampling_factors"][1], images["L"][2]/images["n_voxels"][2]/images["resampling_factors"][2]])
     vtk_image.SetSpacing(spacing)
 
     if   (images["n_dim"] == 1):
@@ -808,42 +176,23 @@ def generateImages(
                 image=vtk_gradient,
                 filename=images["folder"]+"/"+images["basename"]+"-grad"+"_"+str(k_frame).zfill(images["zfill"])+"."+images["ext"],
                 verbose=verbose-1)
+    print "global_min = "+str(global_min)
+    print "global_max = "+str(global_max)
+
+    if (images["resampling_factors"] == [1]*images["n_dim"]):
+        resampling = False
+    else:
+        resampling = True
+        ddic.resample_images(
+            images_folder=images["folder"],
+            images_basename=images["basename"],
+            resampling_factors=images["resampling_factors"],
+            write_temp_images=1)
 
     if (images["data_type"] in ("float")):
         pass
     elif (images["data_type"] in ("unsigned char", "unsigned short", "unsigned int", "unsigned long", "unsigned float", "uint8", "uint16", "uint32", "uint64", "ufloat")):
-        #print "global_min = "+str(global_min)
-        #print "global_max = "+str(global_max)
-        shifter = vtk.vtkImageShiftScale()
-        shifter.SetShift(-global_min)
-        if   (images["data_type"] in ("unsigned char", "uint8")):
-            shifter.SetScale(float(2**8-1)/(global_max-global_min))
-            shifter.SetOutputScalarTypeToUnsignedChar()
-        elif (images["data_type"] in ("unsigned short", "uint16")):
-            shifter.SetScale(float(2**16-1)/(global_max-global_min))
-            shifter.SetOutputScalarTypeToUnsignedShort()
-        elif (images["data_type"] in ("unsigned int", "uint32")):
-            shifter.SetScale(float(2**32-1)/(global_max-global_min))
-            shifter.SetOutputScalarTypeToUnsignedInt()
-        elif (images["data_type"] in ("unsigned long", "uint64")):
-            shifter.SetScale(float(2**64-1)/(global_max-global_min))
-            shifter.SetOutputScalarTypeToUnsignedLong()
-        elif (images["data_type"] in ("unsigned float", "ufloat")):
-            shifter.SetScale(1./(global_max-global_min))
-            shifter.SetOutputScalarTypeToFloat()
-        for k_frame in xrange(images["n_frames"]):
-            vtk_image = myvtk.readImage(
-                filename=images["folder"]+"/"+images["basename"]+"_"+str(k_frame).zfill(images["zfill"])+"."+images["ext"],
-                verbose=verbose-1)
-            if (vtk.vtkVersion.GetVTKMajorVersion() >= 6):
-                shifter.SetInputData(vtk_image)
-            else:
-                shifter.SetInput(vtk_image)
-            shifter.Update()
-            vtk_image = shifter.GetOutput()
-            myvtk.writeImage(
-                image=vtk_image,
-                filename=images["folder"]+"/"+images["basename"]+"_"+str(k_frame).zfill(images["zfill"])+"."+images["ext"],
-                verbose=verbose-1)
-    else:
-        assert (0), "Wrong data type. Aborting."
+        ddic.normalize_images(
+            images_folder=images["folder"],
+            images_basename=images["basename"]+("_resampled"*(resampling)),
+            images_datatype=images["data_type"])
diff --git a/generate_images_Image.py b/generate_images_Image.py
new file mode 100644
index 0000000..6907186
--- /dev/null
+++ b/generate_images_Image.py
@@ -0,0 +1,358 @@
+#coding=utf8
+
+################################################################################
+###                                                                          ###
+### Created by Martin Genet, 2016-2019                                       ###
+###                                                                          ###
+### École Polytechnique, Palaiseau, France                                   ###
+###                                                                          ###
+################################################################################
+
+import glob
+import math
+import numpy
+import os
+import random
+import vtk
+
+import myPythonLibrary as mypy
+import myVTKPythonLibrary as myvtk
+
+import dolfin_dic as ddic
+
+################################################################################
+
+class Image():
+
+    def __init__(self, images, structure, texture, noise, generate_image_gradient=False):
+
+        self.L = images["L"]
+
+        # structure
+        if (structure["type"] == "no"):
+            self.I0_structure = self.I0_structure_no_wGrad if (generate_image_gradient) else self.I0_structure_no
+        elif (structure["type"] == "box"):
+            self.I0_structure = self.I0_structure_box_wGrad if (generate_image_gradient) else self.I0_structure_box
+            self.Xmin = structure["Xmin"]
+            self.Xmax = structure["Xmax"]
+        elif (structure["type"] == "heart"):
+            if (images["n_dim"] == 2):
+                self.I0_structure = self.I0_structure_heart_2_wGrad if (generate_image_gradient) else self.I0_structure_heart_2
+                self.R = float()
+                self.Ri = structure["Ri"]
+                self.Re = structure["Re"]
+            elif (images["n_dim"] == 3):
+                self.I0_structure = self.I0_structure_heart_3_wGrad if (generate_image_gradient) else self.I0_structure_heart_3
+                self.R = float()
+                self.Ri = structure["Ri"]
+                self.Re = structure["Re"]
+                self.Zmin = structure.Zmin if ("Zmin" in structure) else 0.
+                self.Zmax = structure.Zmax if ("Zmax" in structure) else images["L"][2]
+            else:
+                assert (0), "n_dim must be \"2\" or \"3 for \"heart\" type structure. Aborting."
+        else:
+            assert (0), "structure type must be \"no\", \"box\" or \"heart\". Aborting."
+
+        # texture
+        if (texture["type"] == "no"):
+            self.I0_texture = self.I0_texture_no_wGrad if (generate_image_gradient) else self.I0_texture_no
+        elif (texture["type"].startswith("tagging")):
+            if   (images["n_dim"] == 1):
+                if ("-signed" in texture["type"]):
+                    self.I0_texture = self.I0_texture_tagging_signed_X_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_X
+                else:
+                    self.I0_texture = self.I0_texture_tagging_X_wGrad if (generate_image_gradient) else self.I0_texture_tagging_X
+            elif (images["n_dim"] == 2):
+                if ("-signed" in texture["type"]):
+                    if   ("-addComb" in texture["type"]):
+                        self.I0_texture = self.I0_texture_tagging_signed_XY_wAdditiveCombination_wGrad       if (generate_image_gradient) else self.I0_texture_tagging_signed_XY_wAdditiveCombination
+                    elif ("-diffComb" in texture["type"]):
+                        self.I0_texture = self.I0_texture_tagging_signed_XY_wDifferentiableCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_XY_wDifferentiableCombination
+                    else:
+                        self.I0_texture = self.I0_texture_tagging_signed_XY_wMultiplicativeCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_XY_wMultiplicativeCombination
+                else:
+                    if   ("-addComb" in texture["type"]):
+                        self.I0_texture = self.I0_texture_tagging_XY_wAdditiveCombination_wGrad       if (generate_image_gradient) else self.I0_texture_tagging_XY_wAdditiveCombination
+                    elif ("-diffComb" in texture["type"]):
+                        self.I0_texture = self.I0_texture_tagging_XY_wDifferentiableCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_XY_wDifferentiableCombination
+                    else:
+                        self.I0_texture = self.I0_texture_tagging_XY_wMultiplicativeCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_XY_wMultiplicativeCombination
+            elif (images["n_dim"] == 3):
+                if ("-signed" in texture["type"]):
+                    if   ("-addComb" in texture["type"]):
+                        self.I0_texture = self.I0_texture_tagging_signed_XYZ_wAdditiveCombination_wGrad       if (generate_image_gradient) else self.I0_texture_tagging_signed_XYZ_wAdditiveCombination
+                    elif ("-diffComb" in texture["type"]):
+                        self.I0_texture = self.I0_texture_tagging_signed_XYZ_wDifferentiableCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_XYZ_wDifferentiableCombination
+                    else:
+                        self.I0_texture = self.I0_texture_tagging_signed_XYZ_wMultiplicativeCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_XYZ_wMultiplicativeCombination
+                else:
+                    if   ("-addComb" in texture["type"]):
+                        self.I0_texture = self.I0_texture_tagging_XYZ_wAdditiveCombination_wGrad       if (generate_image_gradient) else self.I0_texture_tagging_XYZ_wAdditiveCombination
+                    elif ("-diffComb" in texture["type"]):
+                        self.I0_texture = self.I0_texture_tagging_XYZ_wDifferentiableCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_XYZ_wDifferentiableCombination
+                    else:
+                        self.I0_texture = self.I0_texture_tagging_XYZ_wMultiplicativeCombination_wGrad if (generate_image_gradient) else self.I0_texture_tagging_XYZ_wMultiplicativeCombination
+            else:
+                assert (0), "n_dim must be \"1\", \"2\" or \"3\". Aborting."
+            self.s = texture["s"]
+        elif (texture["type"].startswith("taggX")):
+            if ("-signed" in texture["type"]):
+                self.I0_texture = self.I0_texture_tagging_signed_X_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_X
+            else:
+                self.I0_texture = self.I0_texture_tagging_X_wGrad if (generate_image_gradient) else self.I0_texture_tagging_X
+            self.s = texture["s"]
+        elif (texture["type"].startswith("taggY")):
+            if ("-signed" in texture["type"]):
+                self.I0_texture = self.I0_texture_tagging_signed_Y_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_Y
+            else:
+                self.I0_texture = self.I0_texture_tagging_Y_wGrad if (generate_image_gradient) else self.I0_texture_tagging_Y
+            self.s = texture["s"]
+        elif (texture["type"].startswith("taggZ")):
+            if ("-signed" in texture["type"]):
+                self.I0_texture = self.I0_texture_tagging_signed_Z_wGrad if (generate_image_gradient) else self.I0_texture_tagging_signed_Z
+            else:
+                self.I0_texture = self.I0_texture_tagging_Z_wGrad if (generate_image_gradient) else self.I0_texture_tagging_Z
+            self.s = texture["s"]
+        else:
+            assert (0), "texture type must be \"no\", \"tagging\", \"taggX\", \"taggY\" or \"taggZ\". Aborting."
+
+        # noise
+        if (noise["type"] == "no"):
+            self.I0_noise = self.I0_noise_no_wGrad if (generate_image_gradient) else self.I0_noise_no
+        elif (noise["type"] == "normal"):
+            self.I0_noise = self.I0_noise_normal_wGrad if (generate_image_gradient) else self.I0_noise_normal
+            self.avg = noise["avg"] if ("avg" in noise) else 0.
+            self.std = noise["stdev"]
+        else:
+            assert (0), "noise type must be \"no\" or \"normal\". Aborting."
+
+    def I0(self, X, I):
+        self.I0_structure(X, I)
+        self.I0_texture(X, I)
+        self.I0_noise(I)
+
+    def I0_wGrad(self, X, I, G):
+        self.I0_structure_wGrad(X, I, G)
+        self.I0_texture_wGrad(X, I, G)
+        self.I0_noise_wGrad(I, G)
+
+    def I0_structure_no(self, X, I):
+        I[0] = 1.
+
+    def I0_structure_no_wGrad(self, X, I, G):
+        self.I0_structure_no(X, I)
+        G[:] = 1. # MG 20180806: gradient is given by texture; here it is just indicator function
+
+    def I0_structure_box(self, X, I):
+        if all(numpy.greater_equal(X, self.Xmin)) and all(numpy.less_equal(X, self.Xmax)):
+            I[0] = 1.
+        else:
+            I[0] = 0.
+
+    def I0_structure_box_wGrad(self, X, I, G):
+        if all(numpy.greater_equal(X, self.Xmin)) and all(numpy.less_equal(X, self.Xmax)):
+            I[0] = 1.
+            G[:] = 1. # MG 20180806: gradient is given by texture; here it is just indicator function
+        else:
+            I[0] = 0.
+            G[:] = 0. # MG 20180806: gradient is given by texture; here it is just indicator function
+
+    def I0_structure_heart_2(self, X, I):
+        self.R = ((X[0]-self.L[0]/2)**2 + (X[1]-self.L[1]/2)**2)**(1./2)
+        if (self.R >= self.Ri) and (self.R <= self.Re):
+            I[0] = 1.
+        else:
+            I[0] = 0.
+
+    def I0_structure_heart_2_wGrad(self, X, I, G):
+        self.R = ((X[0]-self.L[0]/2)**2 + (X[1]-self.L[1]/2)**2)**(1./2)
+        if (self.R >= self.Ri) and (self.R <= self.Re):
+            I[0] = 1.
+            G[:] = 1. # MG 20180806: gradient is given by texture; here it is just indicator function
+        else:
+            I[0] = 0.
+            G[:] = 0. # MG 20180806: gradient is given by texture; here it is just indicator function
+
+    def I0_structure_heart_3(self, X, I):
+        self.R = ((X[0]-self.L[0]/2)**2 + (X[1]-self.L[1]/2)**2)**(1./2)
+        if (self.R >= self.Ri) and (self.R <= self.Re) and (X[2] >= self.Zmin) and (X[2] <= self.Zmax):
+            I[0] = 1.
+        else:
+            I[0] = 0.
+
+    def I0_structure_heart_3_wGrad(self, X, I, G):
+        self.R = ((X[0]-self.L[0]/2)**2 + (X[1]-self.L[1]/2)**2)**(1./2)
+        if (self.R >= self.Ri) and (self.R <= self.Re) and (X[2] >= self.Zmin) and (X[2] <= self.Zmax):
+            I[0] = 1.
+            G[:] = 1. # MG 20180806: gradient is given by texture; here it is just indicator function
+        else:
+            I[0] = 0.
+            G[:] = 0. # MG 20180806: gradient is given by texture; here it is just indicator function
+
+    def I0_texture_no(self, X, I):
+        I[0] *= 1.
+
+    def I0_texture_no_wGrad(self, X, I, G):
+        self.I0_texture_no(X, I)
+        G[:] *= 0.
+
+    def I0_texture_tagging_X(self, X, I):
+        I[0] *= abs(math.sin(math.pi*X[0]/self.s))
+
+    def I0_texture_tagging_X_wGrad(self, X, I, G):
+        self.I0_texture_tagging_X(X, I)
+        G[0] *= math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s)
+        G[1] *= 0.
+        G[2] *= 0.
+
+    def I0_texture_tagging_Y(self, X, I):
+        I[0] *= abs(math.sin(math.pi*X[1]/self.s))
+
+    def I0_texture_tagging_Y_wGrad(self, X, I, G):
+        self.I0_texture_tagging_Y(X, I)
+        G[0] *= 0.
+        G[1] *= math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s)
+        G[2] *= 0.
+
+    def I0_texture_tagging_Z(self, X, I):
+        I[0] *= abs(math.sin(math.pi*X[2]/self.s))
+
+    def I0_texture_tagging_Z_wGrad(self, X, I, G):
+        self.I0_texture_tagging_Z(X, I)
+        G[0] *= 0.
+        G[1] *= 0.
+        G[2] *= math.copysign(1, math.sin(math.pi*X[2]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[2]/self.s)
+
+    def I0_texture_tagging_XY_wDifferentiableCombination(self, X, I):
+        I[0] *= (1 + 3 * abs(math.sin(math.pi*X[0]/self.s))
+                       * abs(math.sin(math.pi*X[1]/self.s)))**(1./2) - 1
+
+    def I0_texture_tagging_XY_wDifferentiableCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_XY_wDifferentiableCombination(X, I)
+        G[0] *= 3 * math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) * abs(math.sin(math.pi*X[1]/self.s)) / 2 / (I[0] + 1)
+        G[1] *= 3 * math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) / 2 / (I[0] + 1)
+        G[2] *= 0.
+
+    def I0_texture_tagging_XY_wAdditiveCombination(self, X, I):
+        I[0] *= (abs(math.sin(math.pi*X[0]/self.s))
+               + abs(math.sin(math.pi*X[1]/self.s)))/2
+
+    def I0_texture_tagging_XY_wAdditiveCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_XY_wAdditiveCombination(X, I)
+        G[0] *= math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) / 2
+        G[1] *= math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) / 2
+        G[2] *= 0.
+
+    def I0_texture_tagging_XY_wMultiplicativeCombination(self, X, I):
+        I[0] *= (abs(math.sin(math.pi*X[0]/self.s))
+             *   abs(math.sin(math.pi*X[1]/self.s)))**(1./2)
+
+    def I0_texture_tagging_XY_wMultiplicativeCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_XY_wMultiplicativeCombination(X, I)
+        G[0] *= math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) * abs(math.sin(math.pi*X[1]/self.s)) / 2 / I[0]
+        G[1] *= math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) / 2 / I[0]
+        G[2] *= 0.
+
+    def I0_texture_tagging_XYZ_wDifferentiableCombination(self, X, I):
+        I[0] *= (1 + 7 * abs(math.sin(math.pi*X[0]/self.s))
+                       * abs(math.sin(math.pi*X[1]/self.s))
+                       * abs(math.sin(math.pi*X[2]/self.s)))**(1./3) - 1
+
+    def I0_texture_tagging_XYZ_wDifferentiableCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_XYZ_wDifferentiableCombination(X, I)
+        G[0] *= 7 * math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) * abs(math.sin(math.pi*X[1]/self.s)) * abs(math.sin(math.pi*X[2]/self.s)) / 3 / (I[0] + 1)
+        G[1] *= 7 * math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) * abs(math.sin(math.pi*X[2]/self.s)) / 3 / (I[0] + 1)
+        G[2] *= 7 * math.copysign(1, math.sin(math.pi*X[2]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[2]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) * abs(math.sin(math.pi*X[1]/self.s)) / 3 / (I[0] + 1)
+
+    def I0_texture_tagging_XYZ_wMultiplicativeCombination(self, X, I):
+        I[0] *= (abs(math.sin(math.pi*X[0]/self.s))
+             *   abs(math.sin(math.pi*X[1]/self.s))
+             *   abs(math.sin(math.pi*X[2]/self.s)))**(1./3)
+
+    def I0_texture_tagging_XYZ_wMultiplicativeCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_XYZ_wMultiplicativeCombination(X, I)
+        G[0] *= math.copysign(1, math.sin(math.pi*X[0]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s) * abs(math.sin(math.pi*X[1]/self.s)) * abs(math.sin(math.pi*X[2]/self.s)) / 3 / I[0]**2
+        G[1] *= math.copysign(1, math.sin(math.pi*X[1]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) * abs(math.sin(math.pi*X[2]/self.s)) / 3 / I[0]**2
+        G[2] *= math.copysign(1, math.sin(math.pi*X[2]/self.s)) * (math.pi/self.s) * math.cos(math.pi*X[2]/self.s) * abs(math.sin(math.pi*X[0]/self.s)) * abs(math.sin(math.pi*X[1]/self.s)) / 3 / I[0]**2
+
+    def I0_texture_tagging_signed_X(self, X, I):
+        I[0] *= (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
+
+    def I0_texture_tagging_signed_X_wGrad(self, X, I, G):
+        self.I0_texture_tagging_signed_X(X, I)
+        G[0] *= (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2) / 2
+        G[1] *= 0.
+        G[2] *= 0.
+
+    def I0_texture_tagging_signed_Y(self, X, I):
+        I[0] *= (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2
+
+    def I0_texture_tagging_signed_Y_wGrad(self, X, I, G):
+        self.I0_texture_tagging_signed_Y(X, I)
+        G[0] *= 0.
+        G[1] *= (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2) / 2
+        G[2] *= 0.
+
+    def I0_texture_tagging_signed_Z(self, X, I):
+        I[0] *= (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2
+
+    def I0_texture_tagging_signed_Z_wGrad(self, X, I, G):
+        self.I0_texture_tagging_signed_Z(X, I)
+        G[0] *= 0.
+        G[1] *= 0.
+        G[2] *= (math.pi/self.s) * math.cos(math.pi*X[2]/self.s-math.pi/2) / 2
+
+    def I0_texture_tagging_signed_XY_wDifferentiableCombination(self, X, I):
+        I[0] *= (1 + 3 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
+                       * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2)**(1./2) - 1
+
+    def I0_texture_tagging_signed_XY_wDifferentiableCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_signed_XY_wDifferentiableCombination(X, I)
+        G[0] *= 3 * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2 / 2 / (I[0] + 1)
+        G[1] *= 3 * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2 / 2 / (I[0] + 1)
+        G[2] *= 0.
+
+    def I0_texture_tagging_signed_XY_wAdditiveCombination(self, X, I):
+        I[0] *= ((1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
+              +  (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2) / 2
+
+    def I0_texture_tagging_signed_XY_wAdditiveCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_signed_XY_wAdditiveCombination(X, I)
+        G[0] *= (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2)/2 / 2
+        G[1] *= (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2)/2 / 2
+        G[2] *= 0.
+
+    def I0_texture_tagging_signed_XYZ_wDifferentiableCombination(self, X, I):
+        I[0] *= (1 + 7 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
+                       * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2
+                       * (1+math.sin(math.pi*X[2]/self.s-math.pi/2))/2)**(1./3) - 1
+
+    def I0_texture_tagging_signed_XYZ_wDifferentiableCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_signed_XYZ_wDifferentiableCombination(X, I)
+        G[0] *= 7 * (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2 * (1+math.sin(math.pi*X[2]/self.s-math.pi/2))/2 / 3 / (I[0] + 1)
+        G[1] *= 7 * (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2 * (1+math.sin(math.pi*X[2]/self.s-math.pi/2))/2 / 3 / (I[0] + 1)
+        G[2] *= 7 * (math.pi/self.s) * math.cos(math.pi*X[2]/self.s-math.pi/2)/2 * (1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2 * (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2 / 3 / (I[0] + 1)
+
+    def I0_texture_tagging_signed_XYZ_wAdditiveCombination(self, X, I):
+        I[0] *= ((1+math.sin(math.pi*X[0]/self.s-math.pi/2))/2
+              +  (1+math.sin(math.pi*X[1]/self.s-math.pi/2))/2
+              +  (1+math.sin(math.pi*X[2]/self.s-math.pi/2))/2) / 3
+
+    def I0_texture_tagging_signed_XYZ_wAdditiveCombination_wGrad(self, X, I, G):
+        self.I0_texture_tagging_signed_XYZ_wAdditiveCombination(X, I)
+        G[0] *= (math.pi/self.s) * math.cos(math.pi*X[0]/self.s-math.pi/2)/2 / 3
+        G[1] *= (math.pi/self.s) * math.cos(math.pi*X[1]/self.s-math.pi/2)/2 / 3
+        G[2] *= (math.pi/self.s) * math.cos(math.pi*X[2]/self.s-math.pi/2)/2 / 3
+
+    def I0_noise_no(self, I):
+        pass
+
+    def I0_noise_no_wGrad(self, I, G):
+        pass
+
+    def I0_noise_normal(self, I):
+        I[0] += random.normalvariate(self.avg, self.std)
+
+    def I0_noise_normal_wGrad(self, I, G):
+        self.I0_noise_normal(I)
+        G[:] += [2*random.normalvariate(self.avg, self.std) for k in xrange(len(G))]
diff --git a/generate_images_Mapping.py b/generate_images_Mapping.py
new file mode 100644
index 0000000..5ec3af6
--- /dev/null
+++ b/generate_images_Mapping.py
@@ -0,0 +1,254 @@
+#coding=utf8
+
+################################################################################
+###                                                                          ###
+### Created by Martin Genet, 2016-2019                                       ###
+###                                                                          ###
+### École Polytechnique, Palaiseau, France                                   ###
+###                                                                          ###
+################################################################################
+
+import glob
+import math
+import numpy
+import os
+import random
+import vtk
+
+import myPythonLibrary as mypy
+import myVTKPythonLibrary as myvtk
+
+import dolfin_dic as ddic
+
+################################################################################
+
+class Mapping:
+
+    def __init__(self, images, structure, deformation, evolution, generate_image_gradient):
+
+        self.deformation = deformation
+        if (self.deformation["type"] == "no"):
+            self.init_t = self.init_t_no
+            self.X = self.X_no
+            self.x = self.x_no
+        elif (self.deformation["type"] == "translation"):
+            self.init_t = self.init_t_translation
+            self.X = self.X_translation
+            self.x = self.x_translation
+            self.D = numpy.empty(3)
+        elif (self.deformation["type"] == "rotation"):
+            self.init_t = self.init_t_rotation
+            self.X = self.X_rotation
+            self.x = self.x_rotation
+            self.C = numpy.empty(3)
+            self.R = numpy.empty((3,3))
+            self.Rinv = numpy.empty((3,3))
+        elif (self.deformation["type"] == "homogeneous"):
+            self.init_t = self.init_t_homogeneous
+            self.X = self.X_homogeneous
+            self.x = self.x_homogeneous
+        elif (self.deformation["type"] == "heart"):
+            assert (structure["type"] == "heart"), "structure type must be \"heart\" for \"heart\" type deformation. Aborting."
+            self.init_t = self.init_t_heart
+            self.X = self.X_heart
+            self.x = self.x_heart
+            self.x_inplane = numpy.empty(2)
+            self.X_inplane = numpy.empty(2)
+            self.rt = numpy.empty(2)
+            self.RT = numpy.empty(2)
+            self.L = images["L"]
+            self.Ri = structure["Ri"]
+            self.Re = structure["Re"]
+            self.R = numpy.empty((3,3))
+        else:
+            assert (0), "deformation type must be \"no\", \"translation\", \"rotation\", \"homogeneous\" or \"heart\". Aborting."
+
+        if (evolution["type"] == "linear"):
+            self.phi = self.phi_linear
+        elif (evolution["type"] == "sine"):
+            self.phi = self.phi_sine
+            self.T = evolution["T"]
+        else:
+            assert (0), "evolution ("+evolution["type"]+") type must be \"linear\" or \"sine\". Aborting."
+
+    def phi_linear(self, t):
+        return t
+
+    def phi_sine(self, t):
+        return math.sin(math.pi*t/self.T)**2
+
+    def init_t_no(self, t):
+        pass
+
+    def init_t_translation(self, t):
+        self.D[0] = self.deformation["Dx"] if ("Dx" in self.deformation) else 0.
+        self.D[1] = self.deformation["Dy"] if ("Dy" in self.deformation) else 0.
+        self.D[2] = self.deformation["Dz"] if ("Dz" in self.deformation) else 0.
+        self.D *= self.phi(t)
+
+    def init_t_rotation(self, t):
+        self.C[0] = self.deformation["Cx"] if ("Cx" in self.deformation) else 0.
+        self.C[1] = self.deformation["Cy"] if ("Cy" in self.deformation) else 0.
+        self.C[2] = self.deformation["Cz"] if ("Cz" in self.deformation) else 0.
+        Rx = self.deformation["Rx"]*math.pi/180*self.phi(t) if ("Rx" in self.deformation) else 0.
+        Ry = self.deformation["Ry"]*math.pi/180*self.phi(t) if ("Ry" in self.deformation) else 0.
+        Rz = self.deformation["Rz"]*math.pi/180*self.phi(t) if ("Rz" in self.deformation) else 0.
+        RRx = numpy.array([[          1. ,           0. ,           0. ],
+                           [          0. , +math.cos(Rx), -math.sin(Rx)],
+                           [          0. , +math.sin(Rx), +math.cos(Rx)]])
+        RRy = numpy.array([[+math.cos(Ry),           0. , +math.sin(Ry)],
+                           [          0. ,           1. ,           0. ],
+                           [-math.sin(Ry),           0. , +math.cos(Ry)]])
+        RRz = numpy.array([[+math.cos(Rz), -math.sin(Rz),           0. ],
+                           [+math.sin(Rz), +math.cos(Rz),           0. ],
+                           [          0. ,           0. ,           1. ]])
+        self.R[:,:] = numpy.dot(numpy.dot(RRx, RRy), RRz)
+        self.Rinv[:,:] = numpy.linalg.inv(self.R)
+
+    def init_t_homogeneous(self, t):
+        if (any(E in self.deformation for E in ("Exx", "Eyy", "Ezz"))): # build F from E
+            Exx = self.deformation["Exx"] if ("Exx" in self.deformation) else 0.
+            Eyy = self.deformation["Eyy"] if ("Eyy" in self.deformation) else 0.
+            Ezz = self.deformation["Ezz"] if ("Ezz" in self.deformation) else 0.
+            self.F = numpy.array([[Exx,  0.,  0.],
+                                  [ 0., Eyy,  0.],
+                                  [ 0.,  0., Ezz]])*self.phi(t)
+            self.F *= 2
+            self.F += numpy.eye(3)
+            w, v = numpy.linalg.eig(self.F)
+            # assert (numpy.diag(numpy.dot(numpy.dot(numpy.transpose(v), self.F), v)) == w).all(), str(numpy.dot(numpy.dot(numpy.transpose(v), self.F), v))+" ≠ "+str(numpy.diag(w))+". Aborting."
+            self.F = numpy.dot(numpy.dot(v, numpy.diag(numpy.sqrt(w))), numpy.transpose(v))
+        else:
+            Fxx = self.deformation["Fxx"] if ("Fxx" in self.deformation) else 0.
+            Fyy = self.deformation["Fyy"] if ("Fyy" in self.deformation) else 0.
+            Fzz = self.deformation["Fzz"] if ("Fzz" in self.deformation) else 0.
+            Fxy = self.deformation["Fxy"] if ("Fxy" in self.deformation) else 0.
+            Fyx = self.deformation["Fyx"] if ("Fyx" in self.deformation) else 0.
+            Fyz = self.deformation["Fyz"] if ("Fyz" in self.deformation) else 0.
+            Fzy = self.deformation["Fzy"] if ("Fzy" in self.deformation) else 0.
+            Fzx = self.deformation["Fzx"] if ("Fzx" in self.deformation) else 0.
+            Fxz = self.deformation["Fxz"] if ("Fxz" in self.deformation) else 0.
+            self.F = numpy.eye(3) + (numpy.array([[Fxx, Fxy, Fxz],
+                                                  [Fyx, Fyy, Fyz],
+                                                  [Fzx, Fzy, Fzz]])-numpy.eye(3))*self.phi(t)
+        self.Finv = numpy.linalg.inv(self.F)
+
+    def init_t_heart(self, t):
+        self.dRi = self.deformation["dRi"]*self.phi(t) if ("dRi" in self.deformation) else 0.
+        self.dRe = self.deformation["dRi"]*self.phi(t) if ("dRi" in self.deformation) else 0.
+        self.dTi = self.deformation["dTi"]*self.phi(t) if ("dTi" in self.deformation) else 0.
+        self.dTe = self.deformation["dTe"]*self.phi(t) if ("dTe" in self.deformation) else 0.
+        self.A = numpy.array([[1.-(self.dRi-self.dRe)/(self.Re-self.Ri), 0.],
+                              [  -(self.dTi-self.dTe)/(self.Re-self.Ri), 1.]])
+        self.Ainv = numpy.linalg.inv(self.A)
+        self.B = numpy.array([(1.+self.Ri/(self.Re-self.Ri))*self.dRi-self.Ri/(self.Re-self.Ri)*self.dRe,
+                              (1.+self.Ri/(self.Re-self.Ri))*self.dTi-self.Ri/(self.Re-self.Ri)*self.dTe])
+
+    def X_no(self, x, X, Finv=None):
+        X[:] = x
+        if (Finv is not None): Finv[:,:] = numpy.identity(numpy.sqrt(numpy.size(Finv)))
+
+    def X_translation(self, x, X, Finv=None):
+        X[:] = x - self.D
+        if (Finv is not None): Finv[:,:] = numpy.identity(numpy.sqrt(numpy.size(Finv)))
+
+    def X_rotation(self, x, X, Finv=None):
+        X[:] = numpy.dot(self.Rinv, x - self.C) + self.C
+        if (Finv is not None): Finv[:,:] = self.Rinv
+
+    def X_homogeneous(self, x, X, Finv=None):
+        X[:] = numpy.dot(self.Finv, x)
+        if (Finv is not None): Finv[:,:] = self.Finv
+
+    def X_heart(self, x, X, Finv=None):
+        #print "x = "+str(x)
+        self.x_inplane[0] = x[0] - self.L[0]/2
+        self.x_inplane[1] = x[1] - self.L[1]/2
+        #print "x_inplane = "+str(self.x_inplane)
+        self.rt[0] = numpy.linalg.norm(self.x_inplane)
+        self.rt[1] = math.atan2(self.x_inplane[1], self.x_inplane[0])
+        #print "rt = "+str(self.rt)
+        self.RT[:] = numpy.dot(self.Ainv, self.rt-self.B)
+        #print "RT = "+str(self.RT)
+        X[0] = self.RT[0] * math.cos(self.RT[1]) + self.L[0]/2
+        X[1] = self.RT[0] * math.sin(self.RT[1]) + self.L[1]/2
+        X[2] = x[2]
+        #print "X = "+str(X)
+        if (Finv is not None):
+            Finv[0,0] = 1.+(self.dRe-self.dRi)/(self.Re-self.Ri)
+            Finv[0,1] = 0.
+            Finv[0,2] = 0.
+            Finv[1,0] = (self.dTe-self.dTi)/(self.Re-self.Ri)*self.rt[0]
+            Finv[1,1] = self.rt[0]/self.RT[0]
+            Finv[1,2] = 0.
+            Finv[2,0] = 0.
+            Finv[2,1] = 0.
+            Finv[2,2] = 1.
+            #print "F = "+str(Finv)
+            Finv[:,:] = numpy.linalg.inv(Finv)
+            #print "Finv = "+str(Finv)
+            self.R[0,0] = +math.cos(self.RT[1])
+            self.R[0,1] = +math.sin(self.RT[1])
+            self.R[0,2] = 0.
+            self.R[1,0] = -math.sin(self.RT[1])
+            self.R[1,1] = +math.cos(self.RT[1])
+            self.R[1,2] = 0.
+            self.R[2,0] = 0.
+            self.R[2,1] = 0.
+            self.R[2,2] = 1.
+            #print "R = "+str(self.R)
+            Finv[:] = numpy.dot(numpy.transpose(self.R), numpy.dot(Finv, self.R))
+            #print "Finv = "+str(Finv)
+
+    def x_no(self, X, x, F=None):
+        x[:] = X
+        if (F is not None): F[:,:] = numpy.identity(numpy.sqrt(numpy.size(F)))
+
+    def x_translation(self, X, x, F=None):
+        x[:] = X + self.D
+        if (F is not None): F[:,:] = numpy.identity(numpy.sqrt(numpy.size(F)))
+
+    def x_rotation(self, X, x, F=None):
+        x[:] = numpy.dot(self.R, X - self.C) + self.C
+        if (F is not None): F[:,:] = self.R
+
+    def x_homogeneous(self, X, x, F=None):
+        x[:] = numpy.dot(self.F, X)
+        if (F is not None): F[:,:] = self.F
+
+    def x_heart(self, X, x, F=None):
+        #print "X = "+str(X)
+        self.X_inplane[0] = X[0] - self.L[0]/2
+        self.X_inplane[1] = X[1] - self.L[1]/2
+        #print "X_inplane = "+str(self.X_inplane)
+        self.RT[0] = numpy.linalg.norm(self.X_inplane)
+        self.RT[1] = math.atan2(self.X_inplane[1], self.X_inplane[0])
+        #print "RT = "+str(self.RT)
+        self.rt[:] = numpy.dot(self.A, self.RT) + self.B
+        #print "rt = "+str(self.rt)
+        x[0] = self.rt[0] * math.cos(self.rt[1]) + self.L[0]/2
+        x[1] = self.rt[0] * math.sin(self.rt[1]) + self.L[1]/2
+        x[2] = X[2]
+        #print "x = "+str(x)
+        if (F is not None):
+            F[0,0] = 1.+(self.dRe-self.dRi)/(self.Re-self.Ri)
+            F[0,1] = 0.
+            F[0,2] = 0.
+            F[1,0] = (self.dTe-self.dTi)/(self.Re-self.Ri)*self.rt[0]
+            F[1,1] = self.rt[0]/self.RT[0]
+            F[1,2] = 0.
+            F[2,0] = 0.
+            F[2,1] = 0.
+            F[2,2] = 1.
+            #print "F = "+str(F)
+            self.R[0,0] = +math.cos(self.RT[1])
+            self.R[0,1] = +math.sin(self.RT[1])
+            self.R[0,2] = 0.
+            self.R[1,0] = -math.sin(self.RT[1])
+            self.R[1,1] = +math.cos(self.RT[1])
+            self.R[1,2] = 0.
+            self.R[2,0] = 0.
+            self.R[2,1] = 0.
+            self.R[2,2] = 1.
+            F[:] = numpy.dot(numpy.transpose(self.R), numpy.dot(F, self.R))
+            #print "F = "+str(F)
diff --git a/normalize_images.py b/normalize_images.py
new file mode 100644
index 0000000..edc7296
--- /dev/null
+++ b/normalize_images.py
@@ -0,0 +1,95 @@
+#coding=utf8
+
+################################################################################
+###                                                                          ###
+### Created by Martin Genet, 2016-2019                                       ###
+###                                                                          ###
+### École Polytechnique, Palaiseau, France                                   ###
+###                                                                          ###
+################################################################################
+
+import glob
+import math
+import numpy
+import os
+import random
+import vtk
+
+import myPythonLibrary as mypy
+import myVTKPythonLibrary as myvtk
+
+import dolfin_dic as ddic
+
+################################################################################
+
+def normalize_images(
+        images_folder,
+        images_basename,
+        images_datatype,
+        images_ext="vti"):
+
+    images_filenames = glob.glob(images_folder+"/"+images_basename+"_[0-9]*"+"."+images_ext)
+    images_nframes = len(images_filenames)
+    images_zfill = len(images_filenames[0].rsplit("_",1)[-1].split(".",1)[0])
+
+    image_filename = images_folder+"/"+images_basename+"_"+str(0).zfill(images_zfill)+"."+images_ext
+    image = myvtk.readImage(
+        filename=image_filename,
+        verbose=0)
+    images_npoints = image.GetNumberOfPoints()
+
+    if   (images_ext == "vtk"):
+        reader = vtk.vtkImageReader()
+        writer = vtk.vtkImageWriter()
+    elif (images_ext == "vti"):
+        reader = vtk.vtkXMLImageDataReader()
+        writer = vtk.vtkXMLImageDataWriter()
+    else:
+        assert 0, "\"ext\" must be .vtk or .vti. Aborting."
+
+    global_min = float("+Inf")
+    global_max = float("-Inf")
+    I = numpy.empty(2)
+    for k_frame in xrange(images_nframes):
+        reader.SetFileName(images_folder+"/"+images_basename+"_"+str(k_frame).zfill(images_zfill)+"."+images_ext)
+        reader.Update()
+
+        image_scalars = reader.GetOutput().GetPointData().GetScalars()
+        for k_point in xrange(images_npoints):
+            image_scalars.GetTuple(k_point, I)
+
+            global_min = min(global_min, I[0])
+            global_max = max(global_max, I[0])
+    print "global_min = "+str(global_min)
+    print "global_max = "+str(global_max)
+
+    shifter = vtk.vtkImageShiftScale()
+    shifter.SetInputConnection(reader.GetOutputPort())
+    shifter.SetShift(-global_min)
+    if   (images_datatype in ("unsigned char", "uint8")):
+        shifter.SetScale(float(2**8-1)/(global_max-global_min))
+        shifter.SetOutputScalarTypeToUnsignedChar()
+    elif (images_datatype in ("unsigned short", "uint16")):
+        shifter.SetScale(float(2**16-1)/(global_max-global_min))
+        shifter.SetOutputScalarTypeToUnsignedShort()
+    elif (images_datatype in ("unsigned int", "uint32")):
+        shifter.SetScale(float(2**32-1)/(global_max-global_min))
+        shifter.SetOutputScalarTypeToUnsignedInt()
+    elif (images_datatype in ("unsigned long", "uint64")):
+        shifter.SetScale(float(2**64-1)/(global_max-global_min))
+        shifter.SetOutputScalarTypeToUnsignedLong()
+    elif (images_datatype in ("unsigned float", "ufloat", "float")):
+        shifter.SetScale(1./(global_max-global_min))
+        shifter.SetOutputScalarTypeToFloat()
+
+    writer.SetInputConnection(shifter.GetOutputPort())
+
+    for k_frame in xrange(images_nframes):
+        reader.SetFileName(images_folder+"/"+images_basename+"_"+str(k_frame).zfill(images_zfill)+"."+images_ext)
+        reader.Update()
+
+        shifter.Update()
+
+        writer.SetFileName(images_folder+"/"+images_basename+"_normalized"+"_"+str(k_frame).zfill(images_zfill)+"."+images_ext)
+        writer.Update()
+        writer.Write()
diff --git a/resample_images.py b/resample_images.py
new file mode 100644
index 0000000..6488ec7
--- /dev/null
+++ b/resample_images.py
@@ -0,0 +1,151 @@
+#coding=utf8
+
+################################################################################
+###                                                                          ###
+### Created by Martin Genet, 2016-2019                                       ###
+###                                                                          ###
+### École Polytechnique, Palaiseau, France                                   ###
+###                                                                          ###
+################################################################################
+
+import glob
+import math
+import numpy
+import os
+import random
+import vtk
+
+import myPythonLibrary as mypy
+import myVTKPythonLibrary as myvtk
+
+import dolfin_dic as ddic
+
+################################################################################
+
+def resample_images(
+        images_folder,
+        images_basename,
+        resampling_factors,
+        images_ext="vti",
+        write_temp_images=False):
+
+    images_filenames = glob.glob(images_folder+"/"+images_basename+"_[0-9]*"+"."+images_ext)
+    images_nframes = len(images_filenames)
+    images_zfill = len(images_filenames[0].rsplit("_",1)[-1].split(".",1)[0])
+
+    image_filename = images_folder+"/"+images_basename+"_"+str(0).zfill(images_zfill)+"."+images_ext
+    image = myvtk.readImage(
+        filename=image_filename,
+        verbose=0)
+    images_npoints = image.GetNumberOfPoints()
+    images_nvoxels = myvtk.getImageDimensions(
+        image=image,
+        verbose=0)
+    images_ndim = len(images_nvoxels)
+
+    if   (images_ext == "vtk"):
+        reader = vtk.vtkImageReader()
+        writer = vtk.vtkImageWriter()
+        if (write_temp_images):
+            writer_fft  = vtk.vtkImageWriter()
+            writer_mult = vtk.vtkImageWriter()
+    elif (images_ext == "vti"):
+        reader = vtk.vtkXMLImageDataReader()
+        writer = vtk.vtkXMLImageDataWriter()
+        if (write_temp_images):
+            writer_fft  = vtk.vtkXMLImageDataWriter()
+            writer_mult = vtk.vtkXMLImageDataWriter()
+    else:
+        assert 0, "\"ext\" must be \".vtk\" or \".vti\". Aborting."
+
+    fft = vtk.vtkImageFFT()
+    fft.SetDimensionality(images_ndim)
+    fft.SetInputConnection(reader.GetOutputPort())
+    if (write_temp_images): writer_fft.SetInputConnection(fft.GetOutputPort())
+
+    image_filename = images_folder+"/"+images_basename+"_"+str(0).zfill(images_zfill)+"."+images_ext
+    mask_image = myvtk.readImage(
+        filename=image_filename,
+        verbose=0)
+    mask_scalars = myvtk.createDoubleArray(
+        name="ImageScalars",
+        n_components=2,
+        n_tuples=images_npoints,
+        verbose=0)
+    mask_image.GetPointData().SetScalars(mask_scalars)
+    # print mask_image.GetScalarType()
+    # print mask_image.GetPointData().GetScalars()
+    if (images_ndim == 1):
+        for k_point in xrange(images_npoints):
+            if ((k_point >                     images_nvoxels[0]/resampling_factors[0]/2) \
+            and (k_point < images_nvoxels[0] - images_nvoxels[0]/resampling_factors[0]/2)):
+                mask_scalars.SetTuple(k_point, [0, 0])
+            else:
+                mask_scalars.SetTuple(k_point, [1, 1])
+    if (images_ndim == 2):
+        for k_point in xrange(images_npoints):
+            # print "k_point = "+str(k_point)
+            k_y = math.floor(k_point/images_nvoxels[0])
+            k_x = k_point - k_y*images_nvoxels[0]
+            # print "k_x = "+str(k_x)
+            # print "k_y = "+str(k_y)
+            if (((k_x >                     images_nvoxels[0]/resampling_factors[0]/2)  \
+            and  (k_x < images_nvoxels[0] - images_nvoxels[0]/resampling_factors[0]/2)) \
+            or  ((k_y >                     images_nvoxels[1]/resampling_factors[1]/2)  \
+            and  (k_y < images_nvoxels[1] - images_nvoxels[1]/resampling_factors[1]/2))):
+                mask_scalars.SetTuple(k_point, [0, 0])
+            else:
+                mask_scalars.SetTuple(k_point, [1, 1])
+    if (images_ndim == 3):
+        for k_point in xrange(images_npoints):
+            k_z = math.floor(k_point/images_nvoxels[0]/images_nvoxels[1])
+            k_y = math.floor((k_point - k_z*images_nvoxels[0]*images_nvoxels[1])/images_nvoxels[0])
+            k_x = k_point - k_z*images_nvoxels[0]*images_nvoxels[1] - k_y*images_nvoxels[0]
+            if (((k_x >                     images_nvoxels[0]/resampling_factors[0]/2)  \
+            and  (k_x < images_nvoxels[0] - images_nvoxels[0]/resampling_factors[0]/2)) \
+            or  ((k_y >                     images_nvoxels[1]/resampling_factors[1]/2)  \
+            and  (k_y < images_nvoxels[1] - images_nvoxels[1]/resampling_factors[1]/2)) \
+            or  ((k_z >                     images_nvoxels[2]/resampling_factors[2]/2)  \
+            and  (k_z < images_nvoxels[2] - images_nvoxels[2]/resampling_factors[2]/2))):
+                mask_scalars.SetTuple(k_point, [0, 0])
+            else:
+                mask_scalars.SetTuple(k_point, [1, 1])
+    if (write_temp_images): myvtk.writeImage(
+        image=mask_image,
+        filename=images_folder+"/"+images_basename+"_mask"+"."+images_ext,
+        verbose=0)
+
+    mult = vtk.vtkImageMathematics()
+    mult.SetOperationToMultiply()
+    mult.SetInputData(0, mask_image)
+    mult.SetInputConnection(1, fft.GetOutputPort())
+    if (write_temp_images): writer_mult.SetInputConnection(mult.GetOutputPort())
+
+    rfft = vtk.vtkImageRFFT()
+    rfft.SetDimensionality(images_ndim)
+    rfft.SetInputConnection(mult.GetOutputPort())
+
+    writer.SetInputConnection(rfft.GetOutputPort())
+
+    for k_frame in xrange(images_nframes):
+        reader.SetFileName(images_folder+"/"+images_basename+"_"+str(k_frame).zfill(images_zfill)+"."+images_ext)
+        reader.Update()
+
+        fft.Update()
+        if (write_temp_images):
+            writer_fft.SetFileName(images_folder+"/"+images_basename+"_fft"+"_"+str(k_frame).zfill(images_zfill)+"."+images_ext)
+            writer_fft.Update()
+            writer_fft.Write()
+        # print fft.GetOutput().GetScalarType()
+        # print fft.GetOutput().GetPointData().GetScalars()
+
+        mult.Update()
+        if (write_temp_images):
+            writer_mult.SetFileName(images_folder+"/"+images_basename+"_mult"+"_"+str(k_frame).zfill(images_zfill)+"."+images_ext)
+            writer_mult.Update()
+            writer_mult.Write()
+
+        rfft.Update()
+        writer.SetFileName(images_folder+"/"+images_basename+"_resampled"+"_"+str(k_frame).zfill(images_zfill)+"."+images_ext)
+        writer.Update()
+        writer.Write()
-- 
GitLab