diff --git a/demos/getting_started_mf.py b/demos/getting_started_mf.py
new file mode 100644
index 0000000000000000000000000000000000000000..8aa142b67e862600ef000440d61b52cbd1b7883a
--- /dev/null
+++ b/demos/getting_started_mf.py
@@ -0,0 +1,193 @@
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Copyright(C) 2013-2019 Max-Planck-Society
+#
+# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik.
+
+############################################################
+# Non-linear tomography
+#
+# The signal is a sigmoid-normal distributed field.
+# The data is the field integrated along lines of sight that are
+# randomly (set mode=0) or radially (mode=1) distributed
+#
+# Demo takes a while to compute
+#############################################################
+
+import sys
+
+import numpy as np
+
+import nifty5 as ift
+
+class SingleDomain(ift.LinearOperator):
+    def __init__(self,domain,target):
+        self._domain = ift.makeDomain(domain)
+        self._target = ift.makeDomain(target)
+        self._capability = self.TIMES | self.ADJOINT_TIMES
+    def apply(self,x,mode):
+        self._check_input(x,mode)
+        return ift.from_global_data(self._tgt(mode),x.to_global_data())
+
+def random_los(n_los):
+    starts = list(np.random.uniform(0, 1, (n_los, 2)).T)
+    ends = list(np.random.uniform(0, 1, (n_los, 2)).T)
+    return starts, ends
+
+
+def radial_los(n_los):
+    starts = list(np.random.uniform(0, 1, (n_los, 2)).T)
+    ends = list(0.5 + 0*np.random.uniform(0, 1, (n_los, 2)).T)
+    return starts, ends
+
+
+if __name__ == '__main__':
+    np.random.seed(45)
+
+    # Choose between random line-of-sight response (mode=0) and radial lines
+    # of sight (mode=1)
+    if len(sys.argv) == 2:
+        mode = int(sys.argv[1])
+    else:
+        mode = 0
+    filename = "getting_started_mf_mode_{}_".format(mode) + "{}.png"
+
+    npix1, npix2 = 128, 128
+    position_space = ift.RGSpace([npix1, npix2])
+    sp1 = ift.RGSpace(npix1)
+    sp2 = ift.RGSpace(npix2)
+    
+    power_space1 = ift.PowerSpace(sp1.get_default_codomain())
+    power_space2 = ift.PowerSpace(sp2.get_default_codomain())
+
+    cfmaker = ift.CorrelatedFieldMaker()
+    amp1 = 0.5
+    cfmaker.add_fluctuations(power_space1,
+                             amp1, 1e-2,
+                             1, .1,
+                             .01, .5,
+                             -2, 1.,
+                             'amp1')
+    cfmaker.add_fluctuations(power_space2,
+                             np.sqrt(1.-amp1**2), 1e-2,
+                             1, .1,
+                             .01, .5,
+                             -1.5, .5,
+                             'amp2')
+    correlated_field = cfmaker.finalize(1e-3, 1e-6, '')
+    sams = [ift.from_random('normal',correlated_field.domain)
+            for _ in range(20)]
+
+    print("Prior expected total fluctuations: "+str(
+          cfmaker.stats(cfmaker.total_fluctuation,sams)[0]))
+    
+    A1 = cfmaker.amplitudes[0]
+    A2 = cfmaker.amplitudes[1]
+    DC = SingleDomain(correlated_field.target,position_space)
+
+    # Apply a nonlinearity
+    signal = DC @ ift.sigmoid(correlated_field)
+
+    # Build the line-of-sight response and define signal response
+    LOS_starts, LOS_ends = random_los(100) if mode == 0 else radial_los(100)
+    R = ift.LOSResponse(position_space, starts=LOS_starts, ends=LOS_ends)
+    signal_response = R(signal)
+
+    # Specify noise
+    data_space = R.target
+    noise = .001
+    N = ift.ScalingOperator(noise, data_space)
+
+    # Generate mock signal and data
+    mock_position = ift.from_random('normal', signal_response.domain)
+    data = signal_response(mock_position) + N.draw_sample()
+
+    # Minimization parameters
+    ic_sampling = ift.AbsDeltaEnergyController(
+        name='Sampling', deltaE=0.01, iteration_limit=100)
+    ic_newton = ift.AbsDeltaEnergyController(
+        name='Newton', deltaE=0.01, iteration_limit=35)
+    minimizer = ift.NewtonCG(ic_newton)
+
+    # Set up likelihood and information Hamiltonian
+    likelihood = ift.GaussianEnergy(mean=data,
+                                    inverse_covariance=N.inverse)(signal_response)
+    H = ift.StandardHamiltonian(likelihood, ic_sampling)
+
+    initial_mean = ift.MultiField.full(H.domain, 0.)
+    mean = initial_mean
+
+    plot = ift.Plot()
+    plot.add(signal(mock_position), title='Ground Truth')
+    plot.add(R.adjoint_times(data), title='Data')
+    plot.add([A1.force(mock_position)], title='Power Spectrum 1')
+    plot.add([A2.force(mock_position)], title='Power Spectrum 2')
+    plot.output(ny=2, nx=2, xsize=10, ysize=10, name=filename.format("setup"))
+
+    # number of samples used to estimate the KL
+    N_samples = 20
+
+    # Draw new samples to approximate the KL five times
+    for i in range(10):
+        # Draw new samples and minimize KL
+        KL = ift.MetricGaussianKL(mean, H, N_samples)
+        KL, convergence = minimizer(KL)
+        mean = KL.position
+
+        # Plot current reconstruction
+        plot = ift.Plot()
+        plot.add(signal(mock_position), title="ground truth")
+        plot.add(signal(KL.position), title="reconstruction")
+        plot.add([A1.force(KL.position), A1.force(mock_position)], title="power1")
+        plot.add([A2.force(KL.position), A2.force(mock_position)], title="power2")
+        plot.output(nx = 2, ny=2, ysize=10, xsize=10,
+                    name=filename.format("loop_{:02d}".format(i)))
+
+    # Draw posterior samples
+    Nsamples = 20
+    KL = ift.MetricGaussianKL(mean, H, N_samples)
+    sc = ift.StatCalculator()
+    scA1 = ift.StatCalculator()
+    scA2 = ift.StatCalculator()
+    powers1 = []
+    powers2 = []
+    for sample in KL.samples:
+        sc.add(signal(sample + KL.position))
+        p1 = A1.force(sample + KL.position)
+        p2 = A2.force(sample + KL.position)
+        scA1.add(p1)
+        powers1.append(p1)
+        scA2.add(p2)
+        powers2.append(p2)
+
+    # Plotting
+    filename_res = filename.format("results")
+    plot = ift.Plot()
+    plot.add(sc.mean, title="Posterior Mean")
+    plot.add(ift.sqrt(sc.var), title="Posterior Standard Deviation")
+
+    powers1 = [A1.force(s + KL.position) for s in KL.samples]
+    powers2 = [A2.force(s + KL.position) for s in KL.samples]
+    plot.add(
+        powers1 + [scA1.mean,
+                   A1.force(mock_position)],
+        title="Sampled Posterior Power Spectrum 1",
+        linewidth=[1.]*len(powers1) + [3., 3.])
+    plot.add(
+        powers2 + [scA2.mean,
+                   A2.force(mock_position)],
+        title="Sampled Posterior Power Spectrum 2",
+        linewidth=[1.]*len(powers2) + [3., 3.])
+    plot.output(ny=2, nx=2, xsize=15, ysize=15, name=filename_res)
+    print("Saved results as '{}'.".format(filename_res))