diff --git a/Device/Data/Datafield.cpp b/Device/Data/Datafield.cpp
index c91f2de2b3b3157b7baccd32953651baaabcfcdd..3d20364a3936d32cd98966eccd8605de20f47e4d 100644
--- a/Device/Data/Datafield.cpp
+++ b/Device/Data/Datafield.cpp
@@ -18,6 +18,7 @@
 #include "Base/Axis/Scale.h"
 #include "Base/Util/Assert.h"
 #include <algorithm>
+#include <random>
 
 #ifdef BORNAGAIN_PYTHON
 
@@ -26,6 +27,9 @@
 
 namespace {
 
+auto const seed = 123;
+auto urbg = std::mt19937(seed);
+
 PyObject* npExport(const Frame& frame, const std::vector<double>& flatData)
 {
     if (flatData.empty())
@@ -282,6 +286,11 @@ Datafield* Datafield::crop(double xmin, double xmax) const
 
 #ifdef BORNAGAIN_PYTHON
 
+PyObject* Datafield::npXcenters() const
+{
+    return ::npExport(Frame(xAxis().clone()), xAxis().binCenters());
+}
+
 PyObject* Datafield::npArray() const
 {
     return ::npExport(frame(), flatVector());
@@ -378,3 +387,17 @@ Datafield Datafield::flat() const
 {
     return {title(), frame().flat(), m_values, m_errSigmas};
 }
+
+Datafield Datafield::noisy(double prefactor, double minimum) const
+{
+    std::vector<double> outval(size());
+    std::vector<double> errval(size());
+    for (size_t i = 0; i < size(); ++i) {
+        double mean = valAt(i);
+        double stdv = prefactor * sqrt(mean);
+        auto norm = std::normal_distribution<double>(mean, stdv);
+        outval[i] = std::max(norm(urbg), minimum);
+        errval[i] = stdv;
+    }
+    return {title(), frame().clone(), outval, errval};
+}
diff --git a/Device/Data/Datafield.h b/Device/Data/Datafield.h
index 1fbfb4675d347f2fb494b0fb9e5bf2cc6b512f25..14c1b766854743839475303613ba0a8c98107d1c 100644
--- a/Device/Data/Datafield.h
+++ b/Device/Data/Datafield.h
@@ -64,6 +64,7 @@ public:
     double valAt(size_t i) const;
 #ifdef BORNAGAIN_PYTHON
     //! Returns data as Python numpy array.
+    PyObject* npXcenters() const;
     PyObject* npArray() const;
     PyObject* npErrors() const;
 #endif
@@ -91,6 +92,7 @@ public:
     Datafield plottableField(std::vector<std::string> labels = {}) const;
     Datafield plottableField(std::string label) const;
     Datafield flat() const;
+    Datafield noisy(double prefactor, double minimum) const;
 
     //! Multiplies contents by constant factor, e.g. to shift curves in log plot.
     void scale(double factor);
@@ -133,6 +135,8 @@ public:
     //! Sets content of output data to specific value.
     void setAllTo(const double& value); // Py: test only; rm when possible
 
+    const std::vector<double>& errorSigmas() const;
+
 #ifndef SWIG
     double& operator[](size_t i);
     const double& operator[](size_t i) const;
@@ -141,7 +145,6 @@ public:
     void setVector(const std::vector<double>& data_vector);
 
     bool hasErrorSigmas() const;
-    const std::vector<double>& errorSigmas() const;
     std::vector<double>& errorSigmas();
 
 private:
diff --git a/Wrap/Python/ba_fitmonitor.py b/Wrap/Python/ba_fitmonitor.py
index b199be1f11ca4f314e0b7db7bef9abd26b12d233..bd9c1c01de606fb9b7eaea875aa9e4b330dc0ebd 100644
--- a/Wrap/Python/ba_fitmonitor.py
+++ b/Wrap/Python/ba_fitmonitor.py
@@ -73,18 +73,18 @@ class PlotterGISAS(Plotter):
     def plot(self, fit_objective):
         Plotter.reset(self)
 
-        real_data = fit_objective.experimentalData()
+        data = fit_objective.experimentalData()
         sim_data = fit_objective.simulationResult()
         diff = fit_objective.absoluteDifference()
 
         self.make_subplot(1)
 
         # same limits for both plots
-        arr = real_data.npArray()
+        arr = data.npArray()
         zmax = np.amax(arr) if self._zmax is None else self._zmax
         zmin = zmax*1e-6 if self._zmin is None else self._zmin
 
-        bp.plot_simres(real_data,
+        bp.plot_simres(data,
                        title="Experimental data",
                        intensity_min=zmin,
                        intensity_max=zmax,
diff --git a/auto/Examples/bayesian/likelihood_sampling.py b/auto/Examples/bayesian/likelihood_sampling.py
index e463127a6225d8a720744d9acba0e855758ca393..49ccddbce6b207bafbd3529d7dbd86b16532e1da 100755
--- a/auto/Examples/bayesian/likelihood_sampling.py
+++ b/auto/Examples/bayesian/likelihood_sampling.py
@@ -57,9 +57,9 @@ def run_simulation(points, ni_thickness, ti_thickness):
 
 if __name__ == '__main__':
     filepath = os.path.join(datadir, "specular/genx_alternating_layers.dat.gz")
-    real_data = ba.readData2D(filepath).npArray()
-    real_data[:, 0] *= np.pi/360 # convert incident angles from deg to rad
-    real_data[:, 2] = real_data[:, 1]*0.1 # arbitrary uncertainties of 10%
+    data = ba.readData2D(filepath).npArray()
+    data[:, 0] *= np.pi/360 # convert incident angles from deg to rad
+    data[:, 2] = data[:, 1]*0.1 # arbitrary uncertainties of 10%
 
     def log_likelihood(P):
         """
@@ -70,9 +70,9 @@ if __name__ == '__main__':
         :array yerr: the ordinate uncertainty (dR-values)
         :return: log-likelihood
         """
-        q = real_data[:, 0]
-        y = real_data[:, 1]
-        dy = real_data[:, 2]
+        q = data[:, 0]
+        y = data[:, 1]
+        dy = data[:, 2]
         y_sim = run_simulation(q, *P)
         sigma2 = dy**2 + y_sim**2
         return -0.5*np.sum((y - y_sim)**2/sigma2 + np.log(sigma2))
@@ -104,14 +104,14 @@ if __name__ == '__main__':
     plt.show()
 
     # Plot and show MLE and data of reflectivity
-    plt.errorbar(real_data[:, 0],
-                 real_data[:, 1],
-                 real_data[:, 2],
+    plt.errorbar(data[:, 0],
+                 data[:, 1],
+                 data[:, 2],
                  marker='.',
                  ls='')
     plt.plot(
-        real_data[:, 0],
-        run_simulation(real_data[:, 0], *flat_samples.mean(axis=0)),
+        data[:, 0],
+        run_simulation(data[:, 0], *flat_samples.mean(axis=0)),
         '-')
     plt.xlabel('$\\alpha$/rad')
     plt.ylabel('$R$')
diff --git a/auto/Examples/fit/scatter2d/consecutive_fitting.py b/auto/Examples/fit/scatter2d/consecutive_fitting.py
index 37b1dadb706e3dd018d5b05a6763ffe2f53fa8da..d53f842e29f67681b4f243d61672239c0d8022ab 100755
--- a/auto/Examples/fit/scatter2d/consecutive_fitting.py
+++ b/auto/Examples/fit/scatter2d/consecutive_fitting.py
@@ -50,7 +50,7 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, get_sample(P), detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -59,24 +59,17 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
 
 
 def run_fitting():
     """
     main function to run fitting
     """
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     fit_objective.initPlot(10, observer)
diff --git a/auto/Examples/fit/scatter2d/custom_objective_function.py b/auto/Examples/fit/scatter2d/custom_objective_function.py
index bde5d2abe244199b63d66f25a9e8a5dd98c4730c..00d61fc9e3547e448e9c237556e7a8ba9df9feec 100755
--- a/auto/Examples/fit/scatter2d/custom_objective_function.py
+++ b/auto/Examples/fit/scatter2d/custom_objective_function.py
@@ -32,10 +32,10 @@ class MyObjective(ba.FitObjective):
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     objective = MyObjective()
-    objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    objective.addFitPair(model.get_simulation, data, 1)
     objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/auto/Examples/fit/scatter2d/expfit_galaxi.py b/auto/Examples/fit/scatter2d/expfit_galaxi.py
index e5abf8669b3db63e4c27eec3c1449b912bde47a3..7de6f98915a85ef2e92d1dc9f32ac1fda5cd1773 100755
--- a/auto/Examples/fit/scatter2d/expfit_galaxi.py
+++ b/auto/Examples/fit/scatter2d/expfit_galaxi.py
@@ -122,19 +122,19 @@ def create_simulation(P):
     return simulation
 
 
-def load_real_data(filename):
+def load_data(filename):
     """
     Loads experimental data and returns numpy array.
     """
     filepath = os.path.join(datadir, filename)
-    return ba.readData2D(filepath).npArray()
+    return ba.readData2D(filepath)
 
 
 def run_fitting():
-    real_data = load_real_data("scatter2d/galaxi_data.tif.gz")
+    data = load_data("scatter2d/galaxi_data.tif.gz")
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(create_simulation, real_data, 1)
+    fit_objective.addFitPair(create_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     fit_objective.initPlot(10, observer)
diff --git a/auto/Examples/fit/scatter2d/find_background.py b/auto/Examples/fit/scatter2d/find_background.py
index 2edcbe882b60b5f99326b34ef7cc1e0c92efa69a..72565a617972f409f5a47aa5d92294e6018949cc 100755
--- a/auto/Examples/fit/scatter2d/find_background.py
+++ b/auto/Examples/fit/scatter2d/find_background.py
@@ -24,7 +24,7 @@ def get_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise, background and scale
     to the simulated data.
@@ -42,18 +42,17 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    return result.npArray()
+    return result
 
 
 if __name__ == '__main__':
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
 
     fit_objective.initPrint(10)
-    observer = ba_fitmonitor.PlotterGISAS() 
+    observer = ba_fitmonitor.PlotterGISAS()
     fit_objective.initPlot(10, observer)
 
     P = ba.Parameters()
diff --git a/auto/Examples/fit/scatter2d/fit2d.py b/auto/Examples/fit/scatter2d/fit2d.py
index 72adfaf4706f775e604ed187d08bcf8fb104245d..00a7a3a4e5036df41ad8ac33d9de0e26d6876cd9 100755
--- a/auto/Examples/fit/scatter2d/fit2d.py
+++ b/auto/Examples/fit/scatter2d/fit2d.py
@@ -28,18 +28,19 @@ def get_simulation(P):
     return simulation
 
 
-def real_data():
+def fake_data():
     """
     Generating "experimental" data by running simulation with default parameters.
     """
     simulation = get_simulation({'radius': 5 * nm})
     result = simulation.simulate()
-    return result.npArray()
+    return result
 
 
 if __name__ == '__main__':
+    data = fake_data()
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data())
+    fit_objective.addFitPair(get_simulation, data)
     fit_objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/auto/Examples/fit/scatter2d/fit_along_slices.py b/auto/Examples/fit/scatter2d/fit_along_slices.py
index 6d90bb0b86f5286b2d425a238b3f0b051ec599fc..086302dcc8dc61ccc8824c0bde0e8916da4c3511 100755
--- a/auto/Examples/fit/scatter2d/fit_along_slices.py
+++ b/auto/Examples/fit/scatter2d/fit_along_slices.py
@@ -28,19 +28,18 @@ def get_masked_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
     # initial values which we will have to find later during the fit
     P = {'radius': 5*nm, 'height': 10*nm}
 
-    # retrieving simulated data in the form of numpy array
     simulation = model.get_simulation(P)
     simulation.setBackground(ba.PoissonBackground())
     result = simulation.simulate()
 
-    return result.npArray()
+    return result.noisy(0.1, 0.1)
 
 
 class PlotObserver:
@@ -57,7 +56,7 @@ class PlotObserver:
         self.update(fit_objective)
 
     @staticmethod
-    def plot_real_data(data):
+    def plot_data(data):
         """
         Plot experimental data as heatmap with horizontal/vertical lines
         representing slices on top.
@@ -121,15 +120,15 @@ class PlotObserver:
         """
         self.fig.clf()
 
-        real_data = fit_objective.experimentalData()
+        data = fit_objective.experimentalData()
         simul_data = fit_objective.simulationResult()
 
         # plot real data
         plt.subplot(2, 2, 1)
-        self.plot_real_data(real_data)
+        self.plot_data(data)
 
         # horizontal slices
-        slices = [("real", real_data.xProjection(alpha_slice_value)),
+        slices = [("real", data.xProjection(alpha_slice_value)),
                   ("simul", simul_data.xProjection(alpha_slice_value))]
         title = ("Horizontal slice at alpha =" +
                  '{:3.1f}'.format(alpha_slice_value))
@@ -137,7 +136,7 @@ class PlotObserver:
         self.plot_slices(slices, title)
 
         # vertical slices
-        slices = [("real", real_data.yProjection(phi_slice_value)),
+        slices = [("real", data.yProjection(phi_slice_value)),
                   ("simul", simul_data.yProjection(phi_slice_value))]
         title = "Vertical slice at phi =" + '{:3.1f}'.format(
             phi_slice_value)
@@ -154,13 +153,13 @@ def run_fitting():
     main function to run fitting
     """
 
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_masked_simulation, real_data, 1)
+    fit_objective.addFitPair(get_masked_simulation, data, 1)
     fit_objective.initPrint(10)
 
-    # creating custom observer which will draw fit progress    
+    # creating custom observer which will draw fit progress
     plotter = PlotObserver()
     fit_objective.initPlot(10, plotter)
 
diff --git a/auto/Examples/fit/scatter2d/fit_gisas.py b/auto/Examples/fit/scatter2d/fit_gisas.py
index 584c6338af4a0e51be9a7bdaf38fdea48443b815..253788863da5f4e72cdc0a39b0153d1607684548 100755
--- a/auto/Examples/fit/scatter2d/fit_gisas.py
+++ b/auto/Examples/fit/scatter2d/fit_gisas.py
@@ -13,13 +13,13 @@ from bornagain import ba_fitmonitor
 from matplotlib import pyplot as plt
 
 
-def run_fitting():
+if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
-    real_data = np.loadtxt(os.path.join(datadir, "scatter2d/faked_gisas1.dat.gz"),
-                           dtype=float)
+    fname = os.path.join(datadir, "scatter2d/faked_gisas1.dat.gz")
+    data = ba.readData2D(fname, ba.csv2D)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data)
+    fit_objective.addFitPair(model.get_simulation, data)
 
     fit_objective.initPrint(10)  # Print on every 10th iteration.
     observer = ba_fitmonitor.PlotterGISAS()
@@ -39,8 +39,4 @@ def run_fitting():
 
     # Save simulation image corresponding to the best fit parameters
     np.savetxt("fit.txt", fit_objective.simulationResult().npArray())
-
-
-if __name__ == '__main__':
-    run_fitting()
     plt.show()
diff --git a/auto/Examples/fit/scatter2d/fit_with_masks.py b/auto/Examples/fit/scatter2d/fit_with_masks.py
index fdd070d5d9907926537aefe42868d70667c73e8d..95acd099683df91c9522b7f599ffa81379c0ef46 100755
--- a/auto/Examples/fit/scatter2d/fit_with_masks.py
+++ b/auto/Examples/fit/scatter2d/fit_with_masks.py
@@ -56,10 +56,10 @@ def add_mask_to_simulation(simulation):
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data().noisy(0.1, 0.1)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_masked_simulation, real_data, 1)
+    fit_objective.addFitPair(get_masked_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     fit_objective.initPlot(10, observer)
diff --git a/auto/Examples/fit/scatter2d/lmfit_basics.py b/auto/Examples/fit/scatter2d/lmfit_basics.py
index 4216709ddd27c811ebdea40cefaaecb5fcd0d4a4..ff4a7afe4978a3bfbb572452626fe2b78fa970ca 100755
--- a/auto/Examples/fit/scatter2d/lmfit_basics.py
+++ b/auto/Examples/fit/scatter2d/lmfit_basics.py
@@ -9,10 +9,10 @@ import model2_hexlattice as model
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    fit_objective.addFitPair(model.get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = lmfit.Parameters()
diff --git a/auto/Examples/fit/scatter2d/lmfit_with_plotting.py b/auto/Examples/fit/scatter2d/lmfit_with_plotting.py
index 88e081b70a3810e45f9b9e06b60a8abc72dd81c8..4b2785ee77f4c8d231a8725b5f79fd5e4c9eba46 100755
--- a/auto/Examples/fit/scatter2d/lmfit_with_plotting.py
+++ b/auto/Examples/fit/scatter2d/lmfit_with_plotting.py
@@ -25,16 +25,16 @@ class LMFITPlotter:
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    fit_objective.addFitPair(model.get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = lmfit.Parameters()
     P.add('radius', value=7*nm, min=5*nm, max=8*nm)
     P.add('length', value=10*nm, min=8*nm, max=14*nm)
-    
+
     plotter = LMFITPlotter(fit_objective)
     result = lmfit.minimize(fit_objective.evaluate_residuals,
                             P, iter_cb=plotter)
diff --git a/auto/Examples/fit/scatter2d/minimizer_settings.py b/auto/Examples/fit/scatter2d/minimizer_settings.py
index a95e54328e9348d5c172e9effb52e6f6a19e478f..8d8e31f9d4f007c40c53ff3891f6e280e6014bfd 100755
--- a/auto/Examples/fit/scatter2d/minimizer_settings.py
+++ b/auto/Examples/fit/scatter2d/minimizer_settings.py
@@ -51,7 +51,7 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, get_sample(P), detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data from simulated image with default parameters.
     """
@@ -66,7 +66,7 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    return result.npArray()
+    return result
 
 
 def run_fitting():
@@ -74,7 +74,7 @@ def run_fitting():
     main function to run fitting
     """
 
-    real_data = create_real_data()
+    data = fake_data()
 
     # prints info about available minimizers
     print(ba.MinimizerFactory().catalogToString())
@@ -83,7 +83,7 @@ def run_fitting():
     print(ba.MinimizerFactory().catalogDetailsToString())
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/auto/Examples/fit/scatter2d/model1_cylinders.py b/auto/Examples/fit/scatter2d/model1_cylinders.py
index 789cc95a130fe664746def1931139b93480952e9..bcc2435f299c0e98ea231b61c495afb0bfa200e9 100755
--- a/auto/Examples/fit/scatter2d/model1_cylinders.py
+++ b/auto/Examples/fit/scatter2d/model1_cylinders.py
@@ -42,19 +42,13 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, sample, detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
     P = {'radius': 5*nm, 'height': 10*nm}
 
-    # retrieving simulated data in the form of numpy array
     simulation = get_simulation(P)
     result = simulation.simulate()
-    real_data = result.npArray()
 
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
diff --git a/auto/Examples/fit/scatter2d/model2_hexlattice.py b/auto/Examples/fit/scatter2d/model2_hexlattice.py
index 46e5d9b8f260b31e6b5905c0bde6fc4acd410bf8..5cf760658db0df82d90cd1e57c24b6bfba6980e8 100755
--- a/auto/Examples/fit/scatter2d/model2_hexlattice.py
+++ b/auto/Examples/fit/scatter2d/model2_hexlattice.py
@@ -48,7 +48,7 @@ def get_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -56,12 +56,4 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with noise to produce "real" data
-    np.random.seed(0)
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
diff --git a/auto/Examples/fit/scatter2d/multiple_datasets.py b/auto/Examples/fit/scatter2d/multiple_datasets.py
index 24752b532f5dfd3294c01931765aa7bb510d2b3a..9def2912788efeb28804f15137e2bba9e2ecccdd 100755
--- a/auto/Examples/fit/scatter2d/multiple_datasets.py
+++ b/auto/Examples/fit/scatter2d/multiple_datasets.py
@@ -59,7 +59,7 @@ def simulation2(P):
     return get_simulation(P)
 
 
-def create_real_data(incident_alpha):
+def fake_data(incident_alpha):
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -73,14 +73,7 @@ def create_real_data(incident_alpha):
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
 
 
 class PlotObserver():
@@ -99,14 +92,14 @@ class PlotObserver():
     @staticmethod
     def plot_dataset(fit_objective, canvas):
         for i_dataset in range(0, fit_objective.fitObjectCount()):
-            real_data = fit_objective.experimentalData(i_dataset)
+            data = fit_objective.experimentalData(i_dataset)
             simul_data = fit_objective.simulationResult(i_dataset)
             chi2_map = fit_objective.relativeDifference(i_dataset)
 
-            zmax = real_data.maxVal()
+            zmax = data.maxVal()
 
             plt.subplot(canvas[i_dataset*3])
-            bp.plot_simres(real_data,
+            bp.plot_simres(data,
                              title="\"Real\" data - #" +
                              str(i_dataset + 1),
                              intensity_min=1,
@@ -189,12 +182,12 @@ def run_fitting():
     main function to run fitting
     """
 
-    data1 = create_real_data(0.1*deg)
-    data2 = create_real_data(0.4*deg)
+    data1 = fake_data(0.1*deg)
+    data2 = fake_data(0.4*deg)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(simulation1, data1, 1)
-    fit_objective.addSimulationAndData(simulation2, data2, 1)
+    fit_objective.addFitPair(simulation1, data1, 1)
+    fit_objective.addFitPair(simulation2, data2, 1)
     fit_objective.initPrint(10)
 
     # creating custom observer which will draw fit progress
diff --git a/auto/Examples/fit/specular/Honeycomb_fit.py b/auto/Examples/fit/specular/Honeycomb_fit.py
index c382637f01ec51dcfce62a751af2b11e57ae3687..57cccad8437f825b970aa13f75349acf441bc84b 100755
--- a/auto/Examples/fit/specular/Honeycomb_fit.py
+++ b/auto/Examples/fit/specular/Honeycomb_fit.py
@@ -88,7 +88,7 @@ def run_simulation(qaxis, P, *, sign, T):
 #  Experimental data
 ####################################################################
 
-def get_experimental_data(filename, qmin, qmax):
+def load_data(filename, qmin, qmax):
     # Load from disk.
     filepath = os.path.join(datadir, filename)
     with open(filepath, 'r') as f:
@@ -217,11 +217,11 @@ if __name__ == '__main__':
     qmin = 0.08
     qmax = 1.4
 
-    expData = [
-        get_experimental_data("specular/honeycomb300p.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb300m.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb150p.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb150m.dat", qmin, qmax)]
+    data = [
+        load_data("specular/honeycomb300p.dat", qmin, qmax),
+        load_data("specular/honeycomb300m.dat", qmin, qmax),
+        load_data("specular/honeycomb150p.dat", qmin, qmax),
+        load_data("specular/honeycomb150m.dat", qmin, qmax)]
 
     simFunctions = [
         lambda q, P: run_simulation(q, P, sign=+1, T=300),
@@ -234,14 +234,14 @@ if __name__ == '__main__':
     # Plot data with initial model
 
     simResults = [ f(qzs, P) for f in simFunctions ]
-    plot(qzs, simResults, expData, [1, 1, 10, 10],
+    plot(qzs, simResults, data, [1, 1, 10, 10],
          ["300K $+$", "300K $-$", "150K $+$", "150K $-$"])
     plot_sld_profile(P)
 
     # Fit
 
-    qaxes = [d[0] for d in expData]
-    rdata = [d[1] for d in expData]
+    qaxes = [d[0] for d in data]
+    rdata = [d[1] for d in data]
 
     def par_dict(*args):
         return {name: value for name, value in zip(freeParNames, *args)} | fixedP
@@ -280,7 +280,7 @@ if __name__ == '__main__':
     P = par_dict(result.x)
 
     sim_results = [ f(qzs, P) for f in simFunctions ]
-    plot(qzs, simResults, expData, [1, 1, 10, 10],
+    plot(qzs, simResults, data, [1, 1, 10, 10],
          ["300K $+$", "300K $-$", "150K $+$", "150K $-$"])
     plot_sld_profile(P)
 
diff --git a/auto/Examples/fit/specular/PolarizedSpinAsymmetry.py b/auto/Examples/fit/specular/PolarizedSpinAsymmetry.py
index d2b77fe517a4130d7c86e4234c660cd9c7cd48dc..02b8a45b568af0cf16bc6f1dcd36d4f4525cc756 100755
--- a/auto/Examples/fit/specular/PolarizedSpinAsymmetry.py
+++ b/auto/Examples/fit/specular/PolarizedSpinAsymmetry.py
@@ -118,10 +118,10 @@ def plotData(qs, rs, exps, labels, colors):
     ax = fig.add_subplot(111)
 
     for q, r, exp, l, c in zip(qs, rs, exps, labels, colors):
-        ax.errorbar(exp[0],
-                    exp[1],
-                    xerr=exp[3],
-                    yerr=exp[2],
+        ax.errorbar(exp.xAxis().binCenters(),
+                    exp.flatVector(),
+                    # xerr=TODO i742,
+                    yerr=exp.errorSigmas(),
                     fmt='.',
                     markersize=0.75,
                     linewidth=0.5,
@@ -142,17 +142,19 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
     Plot the simulated spin asymmetry as well its
     experimental counterpart with errorbars
     """
+    Yp = data_pp.npArray()
+    Ym = data_mm.npArray()
+    Ep = data_pp.npErrors()
+    Em = data_mm.npErrors()
     # compute the errorbars of the spin asymmetry
-    delta = numpy.sqrt(4 * (data_pp[1]**2 * data_mm[2]**2 + \
-            data_mm[1]**2 * data_pp[2]**2 ) /
-                ( data_pp[1] + data_mm[1] )**4 )
+    delta = numpy.sqrt(4 * (Yp**2 * Em**2 + Ym**2 * Ep**2 ) / ( Yp + Ym )**4 )
 
     fig = plt.figure()
     ax = fig.add_subplot(111)
 
-    ax.errorbar(data_pp[0],
-                (data_pp[1] - data_mm[1])/(data_pp[1] + data_mm[1]),
-                xerr=data_pp[3],
+    ax.errorbar(data_pp.xAxis().binCenters(),
+                (Yp - Ym) / (Yp + Ym),
+                # xerr=TODO i742,
                 yerr=delta,
                 fmt='.',
                 markersize=0.75,
@@ -172,9 +174,9 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
 #                          Data Handling                           #
 ####################################################################
 
-def load_exp(fname):
-    dat = numpy.loadtxt(fname)
-    return numpy.transpose(dat)
+def load_data(fname):
+    flags = ba.ImportSettings1D("#", "", 1, 2, 3, 4)
+    return ba.readData1D(fname, ba.csv1D, flags)
 
 ####################################################################
 #                          Main Function                           #
@@ -184,8 +186,8 @@ if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
     fname_stem = os.path.join(datadir, "specular/MAFO_Saturated_")
 
-    expdata_pp = load_exp(fname_stem + "pp.tab")
-    expdata_mm = load_exp(fname_stem + "mm.tab")
+    expdata_pp = load_data(fname_stem + "pp.tab")
+    expdata_mm = load_data(fname_stem + "mm.tab")
 
     fixedP = {
         # parameters from our own fit run
diff --git a/auto/Examples/fit/specular/PolarizedSpinAsymmetryFit.py b/auto/Examples/fit/specular/PolarizedSpinAsymmetryFit.py
index 3dc5a42df7dcb11db5a3a5cd8c5831fb15394cdc..5d7cff180c619151042766a1a0fe5233872108c0 100755
--- a/auto/Examples/fit/specular/PolarizedSpinAsymmetryFit.py
+++ b/auto/Examples/fit/specular/PolarizedSpinAsymmetryFit.py
@@ -32,36 +32,6 @@ def get_simulation(q_axis, fitParams, *, polarizer_dir, analyzer_dir):
 
     return simulation
 
-####################################################################
-#                          Fit Function                            #
-####################################################################
-
-def run_fit_ba(q_axis, r_data, r_uncertainty, simulationFactory,
-               startParams):
-
-    fit_objective = ba.FitObjective()
-    fit_objective.setObjectiveMetric("chi2")
-
-    fit_objective.addSimulationAndData(
-        lambda P: simulationFactory[0](q_axis[0], P), r_data[0],
-        r_uncertainty[0], 1)
-    fit_objective.addSimulationAndData(
-        lambda P: simulationFactory[1](q_axis[1], P), r_data[1],
-        r_uncertainty[1], 1)
-
-    fit_objective.initPrint(10)
-
-    P = ba.Parameters()
-    for name, p in startParams.items():
-        P.add(name, p[0], min=p[1], max=p[2])
-
-    minimizer = ba.Minimizer()
-
-    result = minimizer.minimize(fit_objective.evaluate, P)
-    fit_objective.finalize(result)
-
-    return {r.name(): r.value for r in result.parameters()}
-
 ####################################################################
 #                          Main Function                           #
 ####################################################################
@@ -71,8 +41,8 @@ if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
     fname_stem = os.path.join(datadir, "specular/MAFO_Saturated_")
 
-    expdata_pp = psa.load_exp(fname_stem + "pp.tab")
-    expdata_mm = psa.load_exp(fname_stem + "mm.tab")
+    data_pp = psa.load_data(fname_stem + "pp.tab")
+    data_mm = psa.load_data(fname_stem + "mm.tab")
 
     fixedParams = {
         # parameters can be moved here to keep them fixed
@@ -114,26 +84,43 @@ if __name__ == '__main__':
     legend_pp = "$++$"
     legend_mm = "$--$"
 
-    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [expdata_pp, expdata_mm],
+    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [data_pp, data_mm],
              [legend_pp, legend_mm], [color_pp, color_mm])
 
-    psa.plotSpinAsymmetry(expdata_pp, expdata_mm, qzs, r_pp, r_mm)
+    psa.plotSpinAsymmetry(data_pp, data_mm, qzs, r_pp, r_mm)
+
+    fit_objective = ba.FitObjective()
+    fit_objective.setObjectiveMetric("chi2")
+    fit_objective.initPrint(10)
+
+    fit_objective.addFitPair(
+        lambda P: get_Simulation_pp(data_pp.npXcenters(), P),
+        data_pp, 1)
+    fit_objective.addFitPair(
+        lambda P: get_Simulation_mm(data_mm.npXcenters(), P),
+        data_mm, 1)
+
+    P = ba.Parameters()
+    for name, p in startParams.items():
+        P.add(name, p[0], min=p[1], max=p[2])
+
+    minimizer = ba.Minimizer()
+
+    result = minimizer.minimize(fit_objective.evaluate, P)
+    fit_objective.finalize(result)
+
+    fitResult = {r.name(): r.value for r in result.parameters()}
 
-    fitResult = run_fit_ba([expdata_pp[0], expdata_mm[0]],
-                           [expdata_pp[1], expdata_mm[1]],
-                           [expdata_pp[2], expdata_mm[2]],
-                           [get_Simulation_pp, get_Simulation_mm],
-                           startParams)
     print("Fit Result:")
     print(fitResult)
 
     q_pp, r_pp = psa.qr(get_Simulation_pp(qzs, fitResult).simulate())
     q_mm, r_mm = psa.qr(get_Simulation_mm(qzs, fitResult).simulate())
 
-    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [expdata_pp, expdata_mm],
+    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [data_pp, data_mm],
              [legend_pp, legend_mm], [color_pp, color_mm])
     plt.draw()
 
-    psa.plotSpinAsymmetry(expdata_pp, expdata_mm, qzs, r_pp, r_mm)
+    psa.plotSpinAsymmetry(data_pp, data_mm, qzs, r_pp, r_mm)
     plt.draw()
     plt.show()
diff --git a/auto/Examples/fit/specular/Pt_layer_fit.py b/auto/Examples/fit/specular/Pt_layer_fit.py
index ccbcd4f5139ea49c1a60f8cdeb28941558ec974c..a9b82577a8b3665631eb793b3af5daecdedb1e41 100755
--- a/auto/Examples/fit/specular/Pt_layer_fit.py
+++ b/auto/Examples/fit/specular/Pt_layer_fit.py
@@ -60,7 +60,7 @@ def get_simulation(q_axis, P):
 #  Experimental data
 ####################################################################
 
-def get_experimental_data(filename, qmin, qmax):
+def load_data(filename, qmin, qmax):
     filepath = os.path.join(datadir, filename)
     data = np.genfromtxt(filepath, unpack=True)
 
@@ -144,7 +144,7 @@ if __name__ == '__main__':
     qmin = 0.18
     qmax = 2.4
 
-    data = get_experimental_data("specular/RvsQ_36563_36662.dat.gz", qmin, qmax)
+    data = load_data("specular/RvsQ_36563_36662.dat.gz", qmin, qmax)
 
     qzs = np.linspace(qmin, qmax, 1500)
 
diff --git a/auto/Examples/specular/PolarizedSpinAsymmetry.py b/auto/Examples/specular/PolarizedSpinAsymmetry.py
index d2b77fe517a4130d7c86e4234c660cd9c7cd48dc..02b8a45b568af0cf16bc6f1dcd36d4f4525cc756 100755
--- a/auto/Examples/specular/PolarizedSpinAsymmetry.py
+++ b/auto/Examples/specular/PolarizedSpinAsymmetry.py
@@ -118,10 +118,10 @@ def plotData(qs, rs, exps, labels, colors):
     ax = fig.add_subplot(111)
 
     for q, r, exp, l, c in zip(qs, rs, exps, labels, colors):
-        ax.errorbar(exp[0],
-                    exp[1],
-                    xerr=exp[3],
-                    yerr=exp[2],
+        ax.errorbar(exp.xAxis().binCenters(),
+                    exp.flatVector(),
+                    # xerr=TODO i742,
+                    yerr=exp.errorSigmas(),
                     fmt='.',
                     markersize=0.75,
                     linewidth=0.5,
@@ -142,17 +142,19 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
     Plot the simulated spin asymmetry as well its
     experimental counterpart with errorbars
     """
+    Yp = data_pp.npArray()
+    Ym = data_mm.npArray()
+    Ep = data_pp.npErrors()
+    Em = data_mm.npErrors()
     # compute the errorbars of the spin asymmetry
-    delta = numpy.sqrt(4 * (data_pp[1]**2 * data_mm[2]**2 + \
-            data_mm[1]**2 * data_pp[2]**2 ) /
-                ( data_pp[1] + data_mm[1] )**4 )
+    delta = numpy.sqrt(4 * (Yp**2 * Em**2 + Ym**2 * Ep**2 ) / ( Yp + Ym )**4 )
 
     fig = plt.figure()
     ax = fig.add_subplot(111)
 
-    ax.errorbar(data_pp[0],
-                (data_pp[1] - data_mm[1])/(data_pp[1] + data_mm[1]),
-                xerr=data_pp[3],
+    ax.errorbar(data_pp.xAxis().binCenters(),
+                (Yp - Ym) / (Yp + Ym),
+                # xerr=TODO i742,
                 yerr=delta,
                 fmt='.',
                 markersize=0.75,
@@ -172,9 +174,9 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
 #                          Data Handling                           #
 ####################################################################
 
-def load_exp(fname):
-    dat = numpy.loadtxt(fname)
-    return numpy.transpose(dat)
+def load_data(fname):
+    flags = ba.ImportSettings1D("#", "", 1, 2, 3, 4)
+    return ba.readData1D(fname, ba.csv1D, flags)
 
 ####################################################################
 #                          Main Function                           #
@@ -184,8 +186,8 @@ if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
     fname_stem = os.path.join(datadir, "specular/MAFO_Saturated_")
 
-    expdata_pp = load_exp(fname_stem + "pp.tab")
-    expdata_mm = load_exp(fname_stem + "mm.tab")
+    expdata_pp = load_data(fname_stem + "pp.tab")
+    expdata_mm = load_data(fname_stem + "mm.tab")
 
     fixedP = {
         # parameters from our own fit run
diff --git a/auto/Examples/specular/VsGenx.py b/auto/Examples/specular/VsGenx.py
index 54486ebd1ecd512c0653fc35241f674515dce341..c44d0caebfcd3d9a3876b42468adc0bff67df124 100755
--- a/auto/Examples/specular/VsGenx.py
+++ b/auto/Examples/specular/VsGenx.py
@@ -16,7 +16,7 @@ def reference_data(filename):
     """
     Loads and returns reference data from GenX simulation
     """
-    ax_values, real_data = np.loadtxt(filename,
+    ax_values, data = np.loadtxt(filename,
                                       usecols=(0, 1),
                                       skiprows=3,
                                       unpack=True)
@@ -24,7 +24,7 @@ def reference_data(filename):
     # translate axis values from double incident angle to incident angle
     ax_values *= 0.5
 
-    return ax_values, real_data
+    return ax_values, data
 
 
 def get_sample():
diff --git a/auto/MiniExamples/bayesian/likelihood_sampling.py b/auto/MiniExamples/bayesian/likelihood_sampling.py
index aeed815d4919212490203a26060a9699e485ad4e..9144431229378469ed89ad12dba6bc3a0a0165be 100755
--- a/auto/MiniExamples/bayesian/likelihood_sampling.py
+++ b/auto/MiniExamples/bayesian/likelihood_sampling.py
@@ -57,9 +57,9 @@ def run_simulation(points, ni_thickness, ti_thickness):
 
 if __name__ == '__main__':
     filepath = os.path.join(datadir, "specular/genx_alternating_layers.dat.gz")
-    real_data = ba.readData2D(filepath).npArray()
-    real_data[:, 0] *= np.pi/360 # convert incident angles from deg to rad
-    real_data[:, 2] = real_data[:, 1]*0.1 # arbitrary uncertainties of 10%
+    data = ba.readData2D(filepath).npArray()
+    data[:, 0] *= np.pi/360 # convert incident angles from deg to rad
+    data[:, 2] = data[:, 1]*0.1 # arbitrary uncertainties of 10%
 
     def log_likelihood(P):
         """
@@ -70,9 +70,9 @@ if __name__ == '__main__':
         :array yerr: the ordinate uncertainty (dR-values)
         :return: log-likelihood
         """
-        q = real_data[:, 0]
-        y = real_data[:, 1]
-        dy = real_data[:, 2]
+        q = data[:, 0]
+        y = data[:, 1]
+        dy = data[:, 2]
         y_sim = run_simulation(q, *P)
         sigma2 = dy**2 + y_sim**2
         return -0.5*np.sum((y - y_sim)**2/sigma2 + np.log(sigma2))
@@ -104,14 +104,14 @@ if __name__ == '__main__':
     # plt.show()
 
     # Plot and show MLE and data of reflectivity
-    plt.errorbar(real_data[:, 0],
-                 real_data[:, 1],
-                 real_data[:, 2],
+    plt.errorbar(data[:, 0],
+                 data[:, 1],
+                 data[:, 2],
                  marker='.',
                  ls='')
     plt.plot(
-        real_data[:, 0],
-        run_simulation(real_data[:, 0], *flat_samples.mean(axis=0)),
+        data[:, 0],
+        run_simulation(data[:, 0], *flat_samples.mean(axis=0)),
         '-')
     plt.xlabel('$\\alpha$/rad')
     plt.ylabel('$R$')
diff --git a/auto/MiniExamples/fit/scatter2d/consecutive_fitting.py b/auto/MiniExamples/fit/scatter2d/consecutive_fitting.py
index 939026e4e37538a98f0a7f32461ed01de6a41dbe..97c1adeb80325d4831912fde77a917ce39b2a13c 100755
--- a/auto/MiniExamples/fit/scatter2d/consecutive_fitting.py
+++ b/auto/MiniExamples/fit/scatter2d/consecutive_fitting.py
@@ -50,7 +50,7 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, get_sample(P), detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -59,24 +59,17 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
 
 
 def run_fitting():
     """
     main function to run fitting
     """
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     plt.close() # (hide plot) fit_objective.initPlot(10, observer)
diff --git a/auto/MiniExamples/fit/scatter2d/custom_objective_function.py b/auto/MiniExamples/fit/scatter2d/custom_objective_function.py
index bde5d2abe244199b63d66f25a9e8a5dd98c4730c..00d61fc9e3547e448e9c237556e7a8ba9df9feec 100755
--- a/auto/MiniExamples/fit/scatter2d/custom_objective_function.py
+++ b/auto/MiniExamples/fit/scatter2d/custom_objective_function.py
@@ -32,10 +32,10 @@ class MyObjective(ba.FitObjective):
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     objective = MyObjective()
-    objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    objective.addFitPair(model.get_simulation, data, 1)
     objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/auto/MiniExamples/fit/scatter2d/expfit_galaxi.py b/auto/MiniExamples/fit/scatter2d/expfit_galaxi.py
index 05a23a7696fab9198dfb6ba1736b626bde7e613f..b1f7d293c923b9b51a5835b56c081aa7d45af70c 100755
--- a/auto/MiniExamples/fit/scatter2d/expfit_galaxi.py
+++ b/auto/MiniExamples/fit/scatter2d/expfit_galaxi.py
@@ -122,19 +122,19 @@ def create_simulation(P):
     return simulation
 
 
-def load_real_data(filename):
+def load_data(filename):
     """
     Loads experimental data and returns numpy array.
     """
     filepath = os.path.join(datadir, filename)
-    return ba.readData2D(filepath).npArray()
+    return ba.readData2D(filepath)
 
 
 def run_fitting():
-    real_data = load_real_data("scatter2d/galaxi_data.tif.gz")
+    data = load_data("scatter2d/galaxi_data.tif.gz")
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(create_simulation, real_data, 1)
+    fit_objective.addFitPair(create_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     plt.close() # (hide plot) fit_objective.initPlot(10, observer)
diff --git a/auto/MiniExamples/fit/scatter2d/find_background.py b/auto/MiniExamples/fit/scatter2d/find_background.py
index 8d68baa690bb4a2037c8bce768ce3c3e6de5e6e7..adf22b4dee316aa098ba68960ba4c46e933f702a 100755
--- a/auto/MiniExamples/fit/scatter2d/find_background.py
+++ b/auto/MiniExamples/fit/scatter2d/find_background.py
@@ -24,7 +24,7 @@ def get_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise, background and scale
     to the simulated data.
@@ -42,18 +42,17 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    return result.npArray()
+    return result
 
 
 if __name__ == '__main__':
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
 
     fit_objective.initPrint(10)
-    observer = ba_fitmonitor.PlotterGISAS() 
+    observer = ba_fitmonitor.PlotterGISAS()
     plt.close() # (hide plot) fit_objective.initPlot(10, observer)
 
     P = ba.Parameters()
diff --git a/auto/MiniExamples/fit/scatter2d/fit2d.py b/auto/MiniExamples/fit/scatter2d/fit2d.py
index e3c6ffed781197f9d7adf442807dd4ac993eaf9c..f937ac35dac9072cb63ee327297b537febbafb07 100755
--- a/auto/MiniExamples/fit/scatter2d/fit2d.py
+++ b/auto/MiniExamples/fit/scatter2d/fit2d.py
@@ -28,18 +28,19 @@ def get_simulation(P):
     return simulation
 
 
-def real_data():
+def fake_data():
     """
     Generating "experimental" data by running simulation with default parameters.
     """
     simulation = get_simulation({'radius': 5 * nm})
     result = simulation.simulate()
-    return result.npArray()
+    return result
 
 
 if __name__ == '__main__':
+    data = fake_data()
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data())
+    fit_objective.addFitPair(get_simulation, data)
     fit_objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/auto/MiniExamples/fit/scatter2d/fit_along_slices.py b/auto/MiniExamples/fit/scatter2d/fit_along_slices.py
index c4d3b3fd4573a6baade358e61182b87115e61bec..9383f16397d0ed7efda33f804e38dfd3de4778d0 100755
--- a/auto/MiniExamples/fit/scatter2d/fit_along_slices.py
+++ b/auto/MiniExamples/fit/scatter2d/fit_along_slices.py
@@ -28,19 +28,18 @@ def get_masked_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
     # initial values which we will have to find later during the fit
     P = {'radius': 5*nm, 'height': 10*nm}
 
-    # retrieving simulated data in the form of numpy array
     simulation = model.get_simulation(P)
     simulation.setBackground(ba.PoissonBackground())
     result = simulation.simulate()
 
-    return result.npArray()
+    return result.noisy(0.1, 0.1)
 
 
 class PlotObserver:
@@ -57,7 +56,7 @@ class PlotObserver:
         self.update(fit_objective)
 
     @staticmethod
-    def plot_real_data(data):
+    def plot_data(data):
         """
         Plot experimental data as heatmap with horizontal/vertical lines
         representing slices on top.
@@ -121,15 +120,15 @@ class PlotObserver:
         """
         self.fig.clf()
 
-        real_data = fit_objective.experimentalData()
+        data = fit_objective.experimentalData()
         simul_data = fit_objective.simulationResult()
 
         # plot real data
         plt.subplot(2, 2, 1)
-        self.plot_real_data(real_data)
+        self.plot_data(data)
 
         # horizontal slices
-        slices = [("real", real_data.xProjection(alpha_slice_value)),
+        slices = [("real", data.xProjection(alpha_slice_value)),
                   ("simul", simul_data.xProjection(alpha_slice_value))]
         title = ("Horizontal slice at alpha =" +
                  '{:3.1f}'.format(alpha_slice_value))
@@ -137,7 +136,7 @@ class PlotObserver:
         self.plot_slices(slices, title)
 
         # vertical slices
-        slices = [("real", real_data.yProjection(phi_slice_value)),
+        slices = [("real", data.yProjection(phi_slice_value)),
                   ("simul", simul_data.yProjection(phi_slice_value))]
         title = "Vertical slice at phi =" + '{:3.1f}'.format(
             phi_slice_value)
@@ -154,13 +153,13 @@ def run_fitting():
     main function to run fitting
     """
 
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_masked_simulation, real_data, 1)
+    fit_objective.addFitPair(get_masked_simulation, data, 1)
     fit_objective.initPrint(10)
 
-    # creating custom observer which will draw fit progress    
+    # creating custom observer which will draw fit progress
     plotter = PlotObserver()
     plt.close() # (hide plot) fit_objective.initPlot(10, plotter)
 
diff --git a/auto/MiniExamples/fit/scatter2d/fit_gisas.py b/auto/MiniExamples/fit/scatter2d/fit_gisas.py
index 3073a07e3aba00eb638ac5c107c029bfc4d5b807..d890ab2ed4265c2229f3d1c9ec7e5264353c6e0e 100755
--- a/auto/MiniExamples/fit/scatter2d/fit_gisas.py
+++ b/auto/MiniExamples/fit/scatter2d/fit_gisas.py
@@ -13,13 +13,13 @@ from bornagain import ba_fitmonitor
 from matplotlib import pyplot as plt
 
 
-def run_fitting():
+if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
-    real_data = np.loadtxt(os.path.join(datadir, "scatter2d/faked_gisas1.dat.gz"),
-                           dtype=float)
+    fname = os.path.join(datadir, "scatter2d/faked_gisas1.dat.gz")
+    data = ba.readData2D(fname, ba.csv2D)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data)
+    fit_objective.addFitPair(model.get_simulation, data)
 
     fit_objective.initPrint(10)  # Print on every 10th iteration.
     observer = ba_fitmonitor.PlotterGISAS()
@@ -39,8 +39,4 @@ def run_fitting():
 
     # Save simulation image corresponding to the best fit parameters
     np.savetxt("fit.txt", fit_objective.simulationResult().npArray())
-
-
-if __name__ == '__main__':
-    run_fitting()
     plt.show()
diff --git a/auto/MiniExamples/fit/scatter2d/fit_with_masks.py b/auto/MiniExamples/fit/scatter2d/fit_with_masks.py
index 38fe262743b6284447cc442307638c9b1784932f..ca1130b852b4c877b4fafa3c355a0562a895cbd6 100755
--- a/auto/MiniExamples/fit/scatter2d/fit_with_masks.py
+++ b/auto/MiniExamples/fit/scatter2d/fit_with_masks.py
@@ -56,10 +56,10 @@ def add_mask_to_simulation(simulation):
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data().noisy(0.1, 0.1)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_masked_simulation, real_data, 1)
+    fit_objective.addFitPair(get_masked_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     plt.close() # (hide plot) fit_objective.initPlot(10, observer)
diff --git a/auto/MiniExamples/fit/scatter2d/lmfit_basics.py b/auto/MiniExamples/fit/scatter2d/lmfit_basics.py
index 4216709ddd27c811ebdea40cefaaecb5fcd0d4a4..ff4a7afe4978a3bfbb572452626fe2b78fa970ca 100755
--- a/auto/MiniExamples/fit/scatter2d/lmfit_basics.py
+++ b/auto/MiniExamples/fit/scatter2d/lmfit_basics.py
@@ -9,10 +9,10 @@ import model2_hexlattice as model
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    fit_objective.addFitPair(model.get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = lmfit.Parameters()
diff --git a/auto/MiniExamples/fit/scatter2d/lmfit_with_plotting.py b/auto/MiniExamples/fit/scatter2d/lmfit_with_plotting.py
index 4c5903831bd633954fc55543d883026a21cd7b99..643fa35722e31a7fe6fd8dbe278b5b820c9aecfe 100755
--- a/auto/MiniExamples/fit/scatter2d/lmfit_with_plotting.py
+++ b/auto/MiniExamples/fit/scatter2d/lmfit_with_plotting.py
@@ -25,16 +25,16 @@ class LMFITPlotter:
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    fit_objective.addFitPair(model.get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = lmfit.Parameters()
     P.add('radius', value=7*nm, min=5*nm, max=8*nm)
     P.add('length', value=10*nm, min=8*nm, max=14*nm)
-    
+
     
     result = lmfit.minimize(fit_objective.evaluate_residuals,
                             P)
diff --git a/auto/MiniExamples/fit/scatter2d/minimizer_settings.py b/auto/MiniExamples/fit/scatter2d/minimizer_settings.py
index e554661c94c00916bb2bd44f6c64be987d92542b..2093950d49840863a714e4cd1d05ff3b4b173f33 100755
--- a/auto/MiniExamples/fit/scatter2d/minimizer_settings.py
+++ b/auto/MiniExamples/fit/scatter2d/minimizer_settings.py
@@ -51,7 +51,7 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, get_sample(P), detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data from simulated image with default parameters.
     """
@@ -66,7 +66,7 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    return result.npArray()
+    return result
 
 
 def run_fitting():
@@ -74,7 +74,7 @@ def run_fitting():
     main function to run fitting
     """
 
-    real_data = create_real_data()
+    data = fake_data()
 
     # prints info about available minimizers
     print(ba.MinimizerFactory().catalogToString())
@@ -83,7 +83,7 @@ def run_fitting():
     print(ba.MinimizerFactory().catalogDetailsToString())
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/auto/MiniExamples/fit/scatter2d/model1_cylinders.py b/auto/MiniExamples/fit/scatter2d/model1_cylinders.py
index 4ebb3f6a961f374c4aa81c04e004cbe12d0d684c..b2bbf592a80e5762a1d962fc029bd13e82b3e19b 100755
--- a/auto/MiniExamples/fit/scatter2d/model1_cylinders.py
+++ b/auto/MiniExamples/fit/scatter2d/model1_cylinders.py
@@ -42,19 +42,13 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, sample, detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
     P = {'radius': 5*nm, 'height': 10*nm}
 
-    # retrieving simulated data in the form of numpy array
     simulation = get_simulation(P)
     result = simulation.simulate()
-    real_data = result.npArray()
 
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
diff --git a/auto/MiniExamples/fit/scatter2d/model2_hexlattice.py b/auto/MiniExamples/fit/scatter2d/model2_hexlattice.py
index eb30ce743834cf3ffc7e8396792bec6f31cc1bf8..e65c5f0bd9782637c6d6afca225d38915545e4f5 100755
--- a/auto/MiniExamples/fit/scatter2d/model2_hexlattice.py
+++ b/auto/MiniExamples/fit/scatter2d/model2_hexlattice.py
@@ -48,7 +48,7 @@ def get_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -56,12 +56,4 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with noise to produce "real" data
-    np.random.seed(0)
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
diff --git a/auto/MiniExamples/fit/scatter2d/multiple_datasets.py b/auto/MiniExamples/fit/scatter2d/multiple_datasets.py
index 896ce868dcc3a786eab5cd9d19e1b53c2687a592..aa1f4d20cc8d656edc88af848566790ed5e8bfed 100755
--- a/auto/MiniExamples/fit/scatter2d/multiple_datasets.py
+++ b/auto/MiniExamples/fit/scatter2d/multiple_datasets.py
@@ -59,7 +59,7 @@ def simulation2(P):
     return get_simulation(P)
 
 
-def create_real_data(incident_alpha):
+def fake_data(incident_alpha):
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -73,14 +73,7 @@ def create_real_data(incident_alpha):
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
 
 
 class PlotObserver():
@@ -99,14 +92,14 @@ class PlotObserver():
     @staticmethod
     def plot_dataset(fit_objective, canvas):
         for i_dataset in range(0, fit_objective.fitObjectCount()):
-            real_data = fit_objective.experimentalData(i_dataset)
+            data = fit_objective.experimentalData(i_dataset)
             simul_data = fit_objective.simulationResult(i_dataset)
             chi2_map = fit_objective.relativeDifference(i_dataset)
 
-            zmax = real_data.maxVal()
+            zmax = data.maxVal()
 
             plt.subplot(canvas[i_dataset*3])
-            bp.plot_simres(real_data,
+            bp.plot_simres(data,
                              title="\"Real\" data - #" +
                              str(i_dataset + 1),
                              intensity_min=1,
@@ -189,12 +182,12 @@ def run_fitting():
     main function to run fitting
     """
 
-    data1 = create_real_data(0.1*deg)
-    data2 = create_real_data(0.4*deg)
+    data1 = fake_data(0.1*deg)
+    data2 = fake_data(0.4*deg)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(simulation1, data1, 1)
-    fit_objective.addSimulationAndData(simulation2, data2, 1)
+    fit_objective.addFitPair(simulation1, data1, 1)
+    fit_objective.addFitPair(simulation2, data2, 1)
     fit_objective.initPrint(10)
 
     # creating custom observer which will draw fit progress
diff --git a/auto/MiniExamples/fit/specular/Honeycomb_fit.py b/auto/MiniExamples/fit/specular/Honeycomb_fit.py
index 11022ca19f68f7d2cdb56e6fa310d05a45567a46..e258f2b1599275ebafb344850783a0f0cd5373b6 100755
--- a/auto/MiniExamples/fit/specular/Honeycomb_fit.py
+++ b/auto/MiniExamples/fit/specular/Honeycomb_fit.py
@@ -88,7 +88,7 @@ def run_simulation(qaxis, P, *, sign, T):
 #  Experimental data
 ####################################################################
 
-def get_experimental_data(filename, qmin, qmax):
+def load_data(filename, qmin, qmax):
     # Load from disk.
     filepath = os.path.join(datadir, filename)
     with open(filepath, 'r') as f:
@@ -217,11 +217,11 @@ if __name__ == '__main__':
     qmin = 0.08
     qmax = 1.4
 
-    expData = [
-        get_experimental_data("specular/honeycomb300p.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb300m.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb150p.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb150m.dat", qmin, qmax)]
+    data = [
+        load_data("specular/honeycomb300p.dat", qmin, qmax),
+        load_data("specular/honeycomb300m.dat", qmin, qmax),
+        load_data("specular/honeycomb150p.dat", qmin, qmax),
+        load_data("specular/honeycomb150m.dat", qmin, qmax)]
 
     simFunctions = [
         lambda q, P: run_simulation(q, P, sign=+1, T=300),
@@ -234,14 +234,14 @@ if __name__ == '__main__':
     # Plot data with initial model
 
     simResults = [ f(qzs, P) for f in simFunctions ]
-    plot(qzs, simResults, expData, [1, 1, 10, 10],
+    plot(qzs, simResults, data, [1, 1, 10, 10],
          ["300K $+$", "300K $-$", "150K $+$", "150K $-$"])
     plot_sld_profile(P)
 
     # Fit
 
-    qaxes = [d[0] for d in expData]
-    rdata = [d[1] for d in expData]
+    qaxes = [d[0] for d in data]
+    rdata = [d[1] for d in data]
 
     def par_dict(*args):
         return {name: value for name, value in zip(freeParNames, *args)} | fixedP
@@ -280,7 +280,7 @@ if __name__ == '__main__':
     P = par_dict(result.x)
 
     sim_results = [ f(qzs, P) for f in simFunctions ]
-    plot(qzs, simResults, expData, [1, 1, 10, 10],
+    plot(qzs, simResults, data, [1, 1, 10, 10],
          ["300K $+$", "300K $-$", "150K $+$", "150K $-$"])
     plot_sld_profile(P)
 
diff --git a/auto/MiniExamples/fit/specular/PolarizedSpinAsymmetry.py b/auto/MiniExamples/fit/specular/PolarizedSpinAsymmetry.py
index 37510dfc5bcc6471feee1f03dd63f50a1ab87967..d41fc7bd268e3cf251266026f335da70a08a64f2 100755
--- a/auto/MiniExamples/fit/specular/PolarizedSpinAsymmetry.py
+++ b/auto/MiniExamples/fit/specular/PolarizedSpinAsymmetry.py
@@ -118,10 +118,10 @@ def plotData(qs, rs, exps, labels, colors):
     ax = fig.add_subplot(111)
 
     for q, r, exp, l, c in zip(qs, rs, exps, labels, colors):
-        ax.errorbar(exp[0],
-                    exp[1],
-                    xerr=exp[3],
-                    yerr=exp[2],
+        ax.errorbar(exp.xAxis().binCenters(),
+                    exp.flatVector(),
+                    # xerr=TODO i742,
+                    yerr=exp.errorSigmas(),
                     fmt='.',
                     markersize=0.75,
                     linewidth=0.5,
@@ -142,17 +142,19 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
     Plot the simulated spin asymmetry as well its
     experimental counterpart with errorbars
     """
+    Yp = data_pp.npArray()
+    Ym = data_mm.npArray()
+    Ep = data_pp.npErrors()
+    Em = data_mm.npErrors()
     # compute the errorbars of the spin asymmetry
-    delta = numpy.sqrt(4 * (data_pp[1]**2 * data_mm[2]**2 + \
-            data_mm[1]**2 * data_pp[2]**2 ) /
-                ( data_pp[1] + data_mm[1] )**4 )
+    delta = numpy.sqrt(4 * (Yp**2 * Em**2 + Ym**2 * Ep**2 ) / ( Yp + Ym )**4 )
 
     fig = plt.figure()
     ax = fig.add_subplot(111)
 
-    ax.errorbar(data_pp[0],
-                (data_pp[1] - data_mm[1])/(data_pp[1] + data_mm[1]),
-                xerr=data_pp[3],
+    ax.errorbar(data_pp.xAxis().binCenters(),
+                (Yp - Ym) / (Yp + Ym),
+                # xerr=TODO i742,
                 yerr=delta,
                 fmt='.',
                 markersize=0.75,
@@ -172,9 +174,9 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
 #                          Data Handling                           #
 ####################################################################
 
-def load_exp(fname):
-    dat = numpy.loadtxt(fname)
-    return numpy.transpose(dat)
+def load_data(fname):
+    flags = ba.ImportSettings1D("#", "", 1, 2, 3, 4)
+    return ba.readData1D(fname, ba.csv1D, flags)
 
 ####################################################################
 #                          Main Function                           #
@@ -184,8 +186,8 @@ if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
     fname_stem = os.path.join(datadir, "specular/MAFO_Saturated_")
 
-    expdata_pp = load_exp(fname_stem + "pp.tab")
-    expdata_mm = load_exp(fname_stem + "mm.tab")
+    expdata_pp = load_data(fname_stem + "pp.tab")
+    expdata_mm = load_data(fname_stem + "mm.tab")
 
     fixedP = {
         # parameters from our own fit run
diff --git a/auto/MiniExamples/fit/specular/PolarizedSpinAsymmetryFit.py b/auto/MiniExamples/fit/specular/PolarizedSpinAsymmetryFit.py
index 0e75dd5f27032a0afaaab8969de02a27be6707fb..6c3a68be5210699ff3056b4610c3a7b12f31ad95 100755
--- a/auto/MiniExamples/fit/specular/PolarizedSpinAsymmetryFit.py
+++ b/auto/MiniExamples/fit/specular/PolarizedSpinAsymmetryFit.py
@@ -32,36 +32,6 @@ def get_simulation(q_axis, fitParams, *, polarizer_dir, analyzer_dir):
 
     return simulation
 
-####################################################################
-#                          Fit Function                            #
-####################################################################
-
-def run_fit_ba(q_axis, r_data, r_uncertainty, simulationFactory,
-               startParams):
-
-    fit_objective = ba.FitObjective()
-    fit_objective.setObjectiveMetric("chi2")
-
-    fit_objective.addSimulationAndData(
-        lambda P: simulationFactory[0](q_axis[0], P), r_data[0],
-        r_uncertainty[0], 1)
-    fit_objective.addSimulationAndData(
-        lambda P: simulationFactory[1](q_axis[1], P), r_data[1],
-        r_uncertainty[1], 1)
-
-    fit_objective.initPrint(10)
-
-    P = ba.Parameters()
-    for name, p in startParams.items():
-        P.add(name, p[0], min=p[1], max=p[2])
-
-    minimizer = ba.Minimizer()
-
-    result = minimizer.minimize(fit_objective.evaluate, P)
-    fit_objective.finalize(result)
-
-    return {r.name(): r.value for r in result.parameters()}
-
 ####################################################################
 #                          Main Function                           #
 ####################################################################
@@ -71,8 +41,8 @@ if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
     fname_stem = os.path.join(datadir, "specular/MAFO_Saturated_")
 
-    expdata_pp = psa.load_exp(fname_stem + "pp.tab")
-    expdata_mm = psa.load_exp(fname_stem + "mm.tab")
+    data_pp = psa.load_data(fname_stem + "pp.tab")
+    data_mm = psa.load_data(fname_stem + "mm.tab")
 
     fixedParams = {
         # parameters can be moved here to keep them fixed
@@ -114,26 +84,43 @@ if __name__ == '__main__':
     legend_pp = "$++$"
     legend_mm = "$--$"
 
-    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [expdata_pp, expdata_mm],
+    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [data_pp, data_mm],
              [legend_pp, legend_mm], [color_pp, color_mm])
 
-    psa.plotSpinAsymmetry(expdata_pp, expdata_mm, qzs, r_pp, r_mm)
+    psa.plotSpinAsymmetry(data_pp, data_mm, qzs, r_pp, r_mm)
+
+    fit_objective = ba.FitObjective()
+    fit_objective.setObjectiveMetric("chi2")
+    fit_objective.initPrint(10)
+
+    fit_objective.addFitPair(
+        lambda P: get_Simulation_pp(data_pp.npXcenters(), P),
+        data_pp, 1)
+    fit_objective.addFitPair(
+        lambda P: get_Simulation_mm(data_mm.npXcenters(), P),
+        data_mm, 1)
+
+    P = ba.Parameters()
+    for name, p in startParams.items():
+        P.add(name, p[0], min=p[1], max=p[2])
+
+    minimizer = ba.Minimizer()
+
+    result = minimizer.minimize(fit_objective.evaluate, P)
+    fit_objective.finalize(result)
+
+    fitResult = {r.name(): r.value for r in result.parameters()}
 
-    fitResult = run_fit_ba([expdata_pp[0], expdata_mm[0]],
-                           [expdata_pp[1], expdata_mm[1]],
-                           [expdata_pp[2], expdata_mm[2]],
-                           [get_Simulation_pp, get_Simulation_mm],
-                           startParams)
     print("Fit Result:")
     print(fitResult)
 
     q_pp, r_pp = psa.qr(get_Simulation_pp(qzs, fitResult).simulate())
     q_mm, r_mm = psa.qr(get_Simulation_mm(qzs, fitResult).simulate())
 
-    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [expdata_pp, expdata_mm],
+    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [data_pp, data_mm],
              [legend_pp, legend_mm], [color_pp, color_mm])
     #plt.draw()
 
-    psa.plotSpinAsymmetry(expdata_pp, expdata_mm, qzs, r_pp, r_mm)
+    psa.plotSpinAsymmetry(data_pp, data_mm, qzs, r_pp, r_mm)
     #plt.draw()
     #plt.show()
diff --git a/auto/MiniExamples/fit/specular/Pt_layer_fit.py b/auto/MiniExamples/fit/specular/Pt_layer_fit.py
index 8e0063aa1963cd7a2b8ddaaa58bc8d85b6452f23..b64d78e7e0813cd56a865fef7c1bfc5316ee46e1 100755
--- a/auto/MiniExamples/fit/specular/Pt_layer_fit.py
+++ b/auto/MiniExamples/fit/specular/Pt_layer_fit.py
@@ -60,7 +60,7 @@ def get_simulation(q_axis, P):
 #  Experimental data
 ####################################################################
 
-def get_experimental_data(filename, qmin, qmax):
+def load_data(filename, qmin, qmax):
     filepath = os.path.join(datadir, filename)
     data = np.genfromtxt(filepath, unpack=True)
 
@@ -144,7 +144,7 @@ if __name__ == '__main__':
     qmin = 0.18
     qmax = 2.4
 
-    data = get_experimental_data("specular/RvsQ_36563_36662.dat.gz", qmin, qmax)
+    data = load_data("specular/RvsQ_36563_36662.dat.gz", qmin, qmax)
 
     qzs = np.linspace(qmin, qmax, 1500)
 
diff --git a/auto/MiniExamples/specular/PolarizedSpinAsymmetry.py b/auto/MiniExamples/specular/PolarizedSpinAsymmetry.py
index 37510dfc5bcc6471feee1f03dd63f50a1ab87967..d41fc7bd268e3cf251266026f335da70a08a64f2 100755
--- a/auto/MiniExamples/specular/PolarizedSpinAsymmetry.py
+++ b/auto/MiniExamples/specular/PolarizedSpinAsymmetry.py
@@ -118,10 +118,10 @@ def plotData(qs, rs, exps, labels, colors):
     ax = fig.add_subplot(111)
 
     for q, r, exp, l, c in zip(qs, rs, exps, labels, colors):
-        ax.errorbar(exp[0],
-                    exp[1],
-                    xerr=exp[3],
-                    yerr=exp[2],
+        ax.errorbar(exp.xAxis().binCenters(),
+                    exp.flatVector(),
+                    # xerr=TODO i742,
+                    yerr=exp.errorSigmas(),
                     fmt='.',
                     markersize=0.75,
                     linewidth=0.5,
@@ -142,17 +142,19 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
     Plot the simulated spin asymmetry as well its
     experimental counterpart with errorbars
     """
+    Yp = data_pp.npArray()
+    Ym = data_mm.npArray()
+    Ep = data_pp.npErrors()
+    Em = data_mm.npErrors()
     # compute the errorbars of the spin asymmetry
-    delta = numpy.sqrt(4 * (data_pp[1]**2 * data_mm[2]**2 + \
-            data_mm[1]**2 * data_pp[2]**2 ) /
-                ( data_pp[1] + data_mm[1] )**4 )
+    delta = numpy.sqrt(4 * (Yp**2 * Em**2 + Ym**2 * Ep**2 ) / ( Yp + Ym )**4 )
 
     fig = plt.figure()
     ax = fig.add_subplot(111)
 
-    ax.errorbar(data_pp[0],
-                (data_pp[1] - data_mm[1])/(data_pp[1] + data_mm[1]),
-                xerr=data_pp[3],
+    ax.errorbar(data_pp.xAxis().binCenters(),
+                (Yp - Ym) / (Yp + Ym),
+                # xerr=TODO i742,
                 yerr=delta,
                 fmt='.',
                 markersize=0.75,
@@ -172,9 +174,9 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
 #                          Data Handling                           #
 ####################################################################
 
-def load_exp(fname):
-    dat = numpy.loadtxt(fname)
-    return numpy.transpose(dat)
+def load_data(fname):
+    flags = ba.ImportSettings1D("#", "", 1, 2, 3, 4)
+    return ba.readData1D(fname, ba.csv1D, flags)
 
 ####################################################################
 #                          Main Function                           #
@@ -184,8 +186,8 @@ if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
     fname_stem = os.path.join(datadir, "specular/MAFO_Saturated_")
 
-    expdata_pp = load_exp(fname_stem + "pp.tab")
-    expdata_mm = load_exp(fname_stem + "mm.tab")
+    expdata_pp = load_data(fname_stem + "pp.tab")
+    expdata_mm = load_data(fname_stem + "mm.tab")
 
     fixedP = {
         # parameters from our own fit run
diff --git a/auto/MiniExamples/specular/VsGenx.py b/auto/MiniExamples/specular/VsGenx.py
index e5b91dbb2c4f269e5eb6352a980d30651e308438..d0a990a43ab4e7b483208819f1fa9bed0a6f6cb8 100755
--- a/auto/MiniExamples/specular/VsGenx.py
+++ b/auto/MiniExamples/specular/VsGenx.py
@@ -16,7 +16,7 @@ def reference_data(filename):
     """
     Loads and returns reference data from GenX simulation
     """
-    ax_values, real_data = np.loadtxt(filename,
+    ax_values, data = np.loadtxt(filename,
                                       usecols=(0, 1),
                                       skiprows=3,
                                       unpack=True)
@@ -24,7 +24,7 @@ def reference_data(filename):
     # translate axis values from double incident angle to incident angle
     ax_values *= 0.5
 
-    return ax_values, real_data
+    return ax_values, data
 
 
 def get_sample():
diff --git a/auto/Wrap/libBornAgainDevice.py b/auto/Wrap/libBornAgainDevice.py
index f4ee7229eda8294eab7be599dbb159d49a16e127..accb8d77c7a4857481ed01acc8264d5c5402293b 100644
--- a/auto/Wrap/libBornAgainDevice.py
+++ b/auto/Wrap/libBornAgainDevice.py
@@ -2089,6 +2089,10 @@ class Datafield(object):
         r"""valAt(Datafield self, size_t i) -> double"""
         return _libBornAgainDevice.Datafield_valAt(self, i)
 
+    def npXcenters(self):
+        r"""npXcenters(Datafield self) -> PyObject *"""
+        return _libBornAgainDevice.Datafield_npXcenters(self)
+
     def npArray(self):
         r"""npArray(Datafield self) -> PyObject *"""
         return _libBornAgainDevice.Datafield_npArray(self)
@@ -2148,6 +2152,10 @@ class Datafield(object):
         r"""flat(Datafield self) -> Datafield"""
         return _libBornAgainDevice.Datafield_flat(self)
 
+    def noisy(self, prefactor, minimum):
+        r"""noisy(Datafield self, double prefactor, double minimum) -> Datafield"""
+        return _libBornAgainDevice.Datafield_noisy(self, prefactor, minimum)
+
     def scale(self, factor):
         r"""scale(Datafield self, double factor)"""
         return _libBornAgainDevice.Datafield_scale(self, factor)
@@ -2179,6 +2187,10 @@ class Datafield(object):
         r"""setAllTo(Datafield self, double const & value)"""
         return _libBornAgainDevice.Datafield_setAllTo(self, value)
 
+    def errorSigmas(self):
+        r"""errorSigmas(Datafield self) -> vdouble1d_t"""
+        return _libBornAgainDevice.Datafield_errorSigmas(self)
+
 # Register Datafield in _libBornAgainDevice:
 _libBornAgainDevice.Datafield_swigregister(Datafield)
 class Beam(libBornAgainBase.ICloneable, libBornAgainParam.INode):
diff --git a/auto/Wrap/libBornAgainDevice_wrap.cpp b/auto/Wrap/libBornAgainDevice_wrap.cpp
index b9fe28cfe47e09f0d61fdfc3606d54ceded8ae4e..f02b47528cd895e8ce255178a16da14ec7e9f448 100644
--- a/auto/Wrap/libBornAgainDevice_wrap.cpp
+++ b/auto/Wrap/libBornAgainDevice_wrap.cpp
@@ -29680,6 +29680,39 @@ fail:
 }
 
 
+SWIGINTERN PyObject *_wrap_Datafield_npXcenters(PyObject *self, PyObject *args) {
+  PyObject *resultobj = 0;
+  Datafield *arg1 = (Datafield *) 0 ;
+  void *argp1 = 0 ;
+  int res1 = 0 ;
+  PyObject *swig_obj[1] ;
+  PyObject *result = 0 ;
+  
+  if (!args) SWIG_fail;
+  swig_obj[0] = args;
+  res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_Datafield, 0 |  0 );
+  if (!SWIG_IsOK(res1)) {
+    SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Datafield_npXcenters" "', argument " "1"" of type '" "Datafield const *""'"); 
+  }
+  arg1 = reinterpret_cast< Datafield * >(argp1);
+  {
+    try {
+      result = (PyObject *)((Datafield const *)arg1)->npXcenters();
+    } catch (const std::exception& ex) {
+      // message shown in the Python interpreter
+      const std::string msg {
+        "BornAgain C++ Exception: " + std::string(ex.what())
+      };
+      SWIG_exception(SWIG_RuntimeError, msg.c_str());
+    }
+  }
+  resultobj = result;
+  return resultobj;
+fail:
+  return NULL;
+}
+
+
 SWIGINTERN PyObject *_wrap_Datafield_npArray(PyObject *self, PyObject *args) {
   PyObject *resultobj = 0;
   Datafield *arg1 = (Datafield *) 0 ;
@@ -30283,6 +30316,54 @@ fail:
 }
 
 
+SWIGINTERN PyObject *_wrap_Datafield_noisy(PyObject *self, PyObject *args) {
+  PyObject *resultobj = 0;
+  Datafield *arg1 = (Datafield *) 0 ;
+  double arg2 ;
+  double arg3 ;
+  void *argp1 = 0 ;
+  int res1 = 0 ;
+  double val2 ;
+  int ecode2 = 0 ;
+  double val3 ;
+  int ecode3 = 0 ;
+  PyObject *swig_obj[3] ;
+  SwigValueWrapper< Datafield > result;
+  
+  if (!SWIG_Python_UnpackTuple(args, "Datafield_noisy", 3, 3, swig_obj)) SWIG_fail;
+  res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_Datafield, 0 |  0 );
+  if (!SWIG_IsOK(res1)) {
+    SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Datafield_noisy" "', argument " "1"" of type '" "Datafield const *""'"); 
+  }
+  arg1 = reinterpret_cast< Datafield * >(argp1);
+  ecode2 = SWIG_AsVal_double(swig_obj[1], &val2);
+  if (!SWIG_IsOK(ecode2)) {
+    SWIG_exception_fail(SWIG_ArgError(ecode2), "in method '" "Datafield_noisy" "', argument " "2"" of type '" "double""'");
+  } 
+  arg2 = static_cast< double >(val2);
+  ecode3 = SWIG_AsVal_double(swig_obj[2], &val3);
+  if (!SWIG_IsOK(ecode3)) {
+    SWIG_exception_fail(SWIG_ArgError(ecode3), "in method '" "Datafield_noisy" "', argument " "3"" of type '" "double""'");
+  } 
+  arg3 = static_cast< double >(val3);
+  {
+    try {
+      result = ((Datafield const *)arg1)->noisy(arg2,arg3);
+    } catch (const std::exception& ex) {
+      // message shown in the Python interpreter
+      const std::string msg {
+        "BornAgain C++ Exception: " + std::string(ex.what())
+      };
+      SWIG_exception(SWIG_RuntimeError, msg.c_str());
+    }
+  }
+  resultobj = SWIG_NewPointerObj((new Datafield(result)), SWIGTYPE_p_Datafield, SWIG_POINTER_OWN |  0 );
+  return resultobj;
+fail:
+  return NULL;
+}
+
+
 SWIGINTERN PyObject *_wrap_Datafield_scale(PyObject *self, PyObject *args) {
   PyObject *resultobj = 0;
   Datafield *arg1 = (Datafield *) 0 ;
@@ -30907,6 +30988,39 @@ fail:
 }
 
 
+SWIGINTERN PyObject *_wrap_Datafield_errorSigmas(PyObject *self, PyObject *args) {
+  PyObject *resultobj = 0;
+  Datafield *arg1 = (Datafield *) 0 ;
+  void *argp1 = 0 ;
+  int res1 = 0 ;
+  PyObject *swig_obj[1] ;
+  std::vector< double,std::allocator< double > > *result = 0 ;
+  
+  if (!args) SWIG_fail;
+  swig_obj[0] = args;
+  res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_Datafield, 0 |  0 );
+  if (!SWIG_IsOK(res1)) {
+    SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Datafield_errorSigmas" "', argument " "1"" of type '" "Datafield const *""'"); 
+  }
+  arg1 = reinterpret_cast< Datafield * >(argp1);
+  {
+    try {
+      result = (std::vector< double,std::allocator< double > > *) &((Datafield const *)arg1)->errorSigmas();
+    } catch (const std::exception& ex) {
+      // message shown in the Python interpreter
+      const std::string msg {
+        "BornAgain C++ Exception: " + std::string(ex.what())
+      };
+      SWIG_exception(SWIG_RuntimeError, msg.c_str());
+    }
+  }
+  resultobj = swig::from(static_cast< std::vector< double,std::allocator< double > > >(*result));
+  return resultobj;
+fail:
+  return NULL;
+}
+
+
 SWIGINTERN PyObject *Datafield_swigregister(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
   PyObject *obj;
   if (!SWIG_Python_UnpackTuple(args, "swigregister", 1, 1, &obj)) return NULL;
@@ -42711,6 +42825,7 @@ static PyMethodDef SwigMethods[] = {
 	 { "Datafield_title", _wrap_Datafield_title, METH_O, "Datafield_title(Datafield self) -> std::string"},
 	 { "Datafield_setAt", _wrap_Datafield_setAt, METH_VARARGS, "Datafield_setAt(Datafield self, size_t i, double val)"},
 	 { "Datafield_valAt", _wrap_Datafield_valAt, METH_VARARGS, "Datafield_valAt(Datafield self, size_t i) -> double"},
+	 { "Datafield_npXcenters", _wrap_Datafield_npXcenters, METH_O, "Datafield_npXcenters(Datafield self) -> PyObject *"},
 	 { "Datafield_npArray", _wrap_Datafield_npArray, METH_O, "Datafield_npArray(Datafield self) -> PyObject *"},
 	 { "Datafield_npErrors", _wrap_Datafield_npErrors, METH_O, "Datafield_npErrors(Datafield self) -> PyObject *"},
 	 { "Datafield_frame", _wrap_Datafield_frame, METH_O, "Datafield_frame(Datafield self) -> Frame"},
@@ -42728,6 +42843,7 @@ static PyMethodDef SwigMethods[] = {
 		"Datafield_plottableField(Datafield self, std::string label) -> Datafield\n"
 		""},
 	 { "Datafield_flat", _wrap_Datafield_flat, METH_O, "Datafield_flat(Datafield self) -> Datafield"},
+	 { "Datafield_noisy", _wrap_Datafield_noisy, METH_VARARGS, "Datafield_noisy(Datafield self, double prefactor, double minimum) -> Datafield"},
 	 { "Datafield_scale", _wrap_Datafield_scale, METH_VARARGS, "Datafield_scale(Datafield self, double factor)"},
 	 { "Datafield_crop", _wrap_Datafield_crop, METH_VARARGS, "\n"
 		"Datafield_crop(Datafield self, double xmin, double ymin, double xmax, double ymax) -> Datafield\n"
@@ -42744,6 +42860,7 @@ static PyMethodDef SwigMethods[] = {
 		"Datafield_yProjection(Datafield self, double xlow, double xup) -> Datafield\n"
 		""},
 	 { "Datafield_setAllTo", _wrap_Datafield_setAllTo, METH_VARARGS, "Datafield_setAllTo(Datafield self, double const & value)"},
+	 { "Datafield_errorSigmas", _wrap_Datafield_errorSigmas, METH_O, "Datafield_errorSigmas(Datafield self) -> vdouble1d_t"},
 	 { "Datafield_swigregister", Datafield_swigregister, METH_O, NULL},
 	 { "Datafield_swiginit", Datafield_swiginit, METH_VARARGS, NULL},
 	 { "new_Beam", _wrap_new_Beam, METH_VARARGS, "Beam(double intensity, double wavelength, double alpha, double phi=0)"},
diff --git a/rawEx/bayesian/likelihood_sampling.py b/rawEx/bayesian/likelihood_sampling.py
index 51f7e23937257f46adf7e764ce93212660f31b66..ce7f70578fd2690e35a7b13fc586ef6ab8af96c9 100755
--- a/rawEx/bayesian/likelihood_sampling.py
+++ b/rawEx/bayesian/likelihood_sampling.py
@@ -57,9 +57,9 @@ def run_simulation(points, ni_thickness, ti_thickness):
 
 if __name__ == '__main__':
     filepath = os.path.join(datadir, "specular/genx_alternating_layers.dat.gz")
-    real_data = ba.readData2D(filepath).npArray()
-    real_data[:, 0] *= np.pi/360 # convert incident angles from deg to rad
-    real_data[:, 2] = real_data[:, 1]*0.1 # arbitrary uncertainties of 10%
+    data = ba.readData2D(filepath).npArray()
+    data[:, 0] *= np.pi/360 # convert incident angles from deg to rad
+    data[:, 2] = data[:, 1]*0.1 # arbitrary uncertainties of 10%
 
     def log_likelihood(P):
         """
@@ -70,9 +70,9 @@ if __name__ == '__main__':
         :array yerr: the ordinate uncertainty (dR-values)
         :return: log-likelihood
         """
-        q = real_data[:, 0]
-        y = real_data[:, 1]
-        dy = real_data[:, 2]
+        q = data[:, 0]
+        y = data[:, 1]
+        dy = data[:, 2]
         y_sim = run_simulation(q, *P)
         sigma2 = dy**2 + y_sim**2
         return -0.5*np.sum((y - y_sim)**2/sigma2 + np.log(sigma2))
@@ -104,14 +104,14 @@ if __name__ == '__main__':
     <%= sm ? "# " : "" %>plt.show()
 
     # Plot and show MLE and data of reflectivity
-    plt.errorbar(real_data[:, 0],
-                 real_data[:, 1],
-                 real_data[:, 2],
+    plt.errorbar(data[:, 0],
+                 data[:, 1],
+                 data[:, 2],
                  marker='.',
                  ls='')
     plt.plot(
-        real_data[:, 0],
-        run_simulation(real_data[:, 0], *flat_samples.mean(axis=0)),
+        data[:, 0],
+        run_simulation(data[:, 0], *flat_samples.mean(axis=0)),
         '-')
     plt.xlabel('$\\alpha$/rad')
     plt.ylabel('$R$')
diff --git a/rawEx/fit/scatter2d/consecutive_fitting.py b/rawEx/fit/scatter2d/consecutive_fitting.py
index ea875fa9ae82b54b3bc62c6fd0347180396370a6..74ab218391ffbc0bd9c7d7db107b4c5e670999d1 100755
--- a/rawEx/fit/scatter2d/consecutive_fitting.py
+++ b/rawEx/fit/scatter2d/consecutive_fitting.py
@@ -50,7 +50,7 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, get_sample(P), detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -59,24 +59,17 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
 
 
 def run_fitting():
     """
     main function to run fitting
     """
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     <%= sm ? "plt.close() # (hide plot) " :"" %>fit_objective.initPlot(10, observer)
diff --git a/rawEx/fit/scatter2d/custom_objective_function.py b/rawEx/fit/scatter2d/custom_objective_function.py
index bde5d2abe244199b63d66f25a9e8a5dd98c4730c..00d61fc9e3547e448e9c237556e7a8ba9df9feec 100755
--- a/rawEx/fit/scatter2d/custom_objective_function.py
+++ b/rawEx/fit/scatter2d/custom_objective_function.py
@@ -32,10 +32,10 @@ class MyObjective(ba.FitObjective):
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     objective = MyObjective()
-    objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    objective.addFitPair(model.get_simulation, data, 1)
     objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/rawEx/fit/scatter2d/expfit_galaxi.py b/rawEx/fit/scatter2d/expfit_galaxi.py
index 41eb75ca250b06aa9b9e929488fb6755ca905c21..6e39dead1c3d2fa07546010d1aee49b31160e1c8 100755
--- a/rawEx/fit/scatter2d/expfit_galaxi.py
+++ b/rawEx/fit/scatter2d/expfit_galaxi.py
@@ -122,19 +122,19 @@ def create_simulation(P):
     return simulation
 
 
-def load_real_data(filename):
+def load_data(filename):
     """
     Loads experimental data and returns numpy array.
     """
     filepath = os.path.join(datadir, filename)
-    return ba.readData2D(filepath).npArray()
+    return ba.readData2D(filepath)
 
 
 def run_fitting():
-    real_data = load_real_data("scatter2d/galaxi_data.tif.gz")
+    data = load_data("scatter2d/galaxi_data.tif.gz")
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(create_simulation, real_data, 1)
+    fit_objective.addFitPair(create_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     <%= sm ? "plt.close() # (hide plot) " :"" %>fit_objective.initPlot(10, observer)
diff --git a/rawEx/fit/scatter2d/find_background.py b/rawEx/fit/scatter2d/find_background.py
index f864ec53d2ded5354201c18ee9bcc9b1e7745cb2..8720eb52d8d6f95aa9efa421f2fdd582a82129fb 100755
--- a/rawEx/fit/scatter2d/find_background.py
+++ b/rawEx/fit/scatter2d/find_background.py
@@ -24,7 +24,7 @@ def get_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise, background and scale
     to the simulated data.
@@ -42,18 +42,17 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    return result.npArray()
+    return result
 
 
 if __name__ == '__main__':
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
 
     fit_objective.initPrint(10)
-    observer = ba_fitmonitor.PlotterGISAS() 
+    observer = ba_fitmonitor.PlotterGISAS()
     <%= sm ? "plt.close() # (hide plot) " :"" %>fit_objective.initPlot(10, observer)
 
     P = ba.Parameters()
diff --git a/rawEx/fit/scatter2d/fit2d.py b/rawEx/fit/scatter2d/fit2d.py
index 08734f6cab3fe559c71062cf3e914d290a0cbbb4..205bac5e0dc06059744647ca8aa9d25e2a237b61 100755
--- a/rawEx/fit/scatter2d/fit2d.py
+++ b/rawEx/fit/scatter2d/fit2d.py
@@ -28,18 +28,19 @@ def get_simulation(P):
     return simulation
 
 
-def real_data():
+def fake_data():
     """
     Generating "experimental" data by running simulation with default parameters.
     """
     simulation = get_simulation({'radius': 5 * nm})
     result = simulation.simulate()
-    return result.npArray()
+    return result
 
 
 if __name__ == '__main__':
+    data = fake_data()
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data())
+    fit_objective.addFitPair(get_simulation, data)
     fit_objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/rawEx/fit/scatter2d/fit_along_slices.py b/rawEx/fit/scatter2d/fit_along_slices.py
index 3de58250cdef41e1d8960be336ea11ba194b642a..da6989c7adb1df19204ddd275500dfc98288d484 100755
--- a/rawEx/fit/scatter2d/fit_along_slices.py
+++ b/rawEx/fit/scatter2d/fit_along_slices.py
@@ -28,19 +28,18 @@ def get_masked_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
     # initial values which we will have to find later during the fit
     P = {'radius': 5*nm, 'height': 10*nm}
 
-    # retrieving simulated data in the form of numpy array
     simulation = model.get_simulation(P)
     simulation.setBackground(ba.PoissonBackground())
     result = simulation.simulate()
 
-    return result.npArray()
+    return result.noisy(0.1, 0.1)
 
 
 class PlotObserver:
@@ -57,7 +56,7 @@ class PlotObserver:
         self.update(fit_objective)
 
     @staticmethod
-    def plot_real_data(data):
+    def plot_data(data):
         """
         Plot experimental data as heatmap with horizontal/vertical lines
         representing slices on top.
@@ -121,15 +120,15 @@ class PlotObserver:
         """
         self.fig.clf()
 
-        real_data = fit_objective.experimentalData()
+        data = fit_objective.experimentalData()
         simul_data = fit_objective.simulationResult()
 
         # plot real data
         plt.subplot(2, 2, 1)
-        self.plot_real_data(real_data)
+        self.plot_data(data)
 
         # horizontal slices
-        slices = [("real", real_data.xProjection(alpha_slice_value)),
+        slices = [("real", data.xProjection(alpha_slice_value)),
                   ("simul", simul_data.xProjection(alpha_slice_value))]
         title = ("Horizontal slice at alpha =" +
                  '{:3.1f}'.format(alpha_slice_value))
@@ -137,7 +136,7 @@ class PlotObserver:
         self.plot_slices(slices, title)
 
         # vertical slices
-        slices = [("real", real_data.yProjection(phi_slice_value)),
+        slices = [("real", data.yProjection(phi_slice_value)),
                   ("simul", simul_data.yProjection(phi_slice_value))]
         title = "Vertical slice at phi =" + '{:3.1f}'.format(
             phi_slice_value)
@@ -154,13 +153,13 @@ def run_fitting():
     main function to run fitting
     """
 
-    real_data = create_real_data()
+    data = fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_masked_simulation, real_data, 1)
+    fit_objective.addFitPair(get_masked_simulation, data, 1)
     fit_objective.initPrint(10)
 
-    # creating custom observer which will draw fit progress    
+    # creating custom observer which will draw fit progress
     plotter = PlotObserver()
     <%= sm ? "plt.close() # (hide plot) " :"" %>fit_objective.initPlot(10, plotter)
 
diff --git a/rawEx/fit/scatter2d/fit_gisas.py b/rawEx/fit/scatter2d/fit_gisas.py
index a255f1df1801bb4270efc726f1570f42d20504a9..bdba0108255aa4504e117a2830ae33ffe086fcfa 100755
--- a/rawEx/fit/scatter2d/fit_gisas.py
+++ b/rawEx/fit/scatter2d/fit_gisas.py
@@ -13,13 +13,13 @@ from bornagain import ba_fitmonitor
 from matplotlib import pyplot as plt
 
 
-def run_fitting():
+if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
-    real_data = np.loadtxt(os.path.join(datadir, "scatter2d/faked_gisas1.dat.gz"),
-                           dtype=float)
+    fname = os.path.join(datadir, "scatter2d/faked_gisas1.dat.gz")
+    data = ba.readData2D(fname, ba.csv2D)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data)
+    fit_objective.addFitPair(model.get_simulation, data)
 
     fit_objective.initPrint(10)  # Print on every 10th iteration.
     observer = ba_fitmonitor.PlotterGISAS()
@@ -39,8 +39,4 @@ def run_fitting():
 
     # Save simulation image corresponding to the best fit parameters
     np.savetxt("fit.txt", fit_objective.simulationResult().npArray())
-
-
-if __name__ == '__main__':
-    run_fitting()
     plt.show()
diff --git a/rawEx/fit/scatter2d/fit_with_masks.py b/rawEx/fit/scatter2d/fit_with_masks.py
index 2eabe4707478dbdcf6dd57b88142fe1b8b4ad381..aaf0159d64602932b5ee0a1812ba5dff86f3bb0a 100755
--- a/rawEx/fit/scatter2d/fit_with_masks.py
+++ b/rawEx/fit/scatter2d/fit_with_masks.py
@@ -56,10 +56,10 @@ def add_mask_to_simulation(simulation):
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data().noisy(0.1, 0.1)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_masked_simulation, real_data, 1)
+    fit_objective.addFitPair(get_masked_simulation, data, 1)
     fit_objective.initPrint(10)
     observer = ba_fitmonitor.PlotterGISAS()
     <%= sm ? "plt.close() # (hide plot) " :"" %>fit_objective.initPlot(10, observer)
diff --git a/rawEx/fit/scatter2d/lmfit_basics.py b/rawEx/fit/scatter2d/lmfit_basics.py
index 4216709ddd27c811ebdea40cefaaecb5fcd0d4a4..ff4a7afe4978a3bfbb572452626fe2b78fa970ca 100755
--- a/rawEx/fit/scatter2d/lmfit_basics.py
+++ b/rawEx/fit/scatter2d/lmfit_basics.py
@@ -9,10 +9,10 @@ import model2_hexlattice as model
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    fit_objective.addFitPair(model.get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = lmfit.Parameters()
diff --git a/rawEx/fit/scatter2d/lmfit_with_plotting.py b/rawEx/fit/scatter2d/lmfit_with_plotting.py
index 2001282a54ce167b25179a3e1fc11d60dc7259d6..7e68bd8c35b0412ae8c948a426cc0d5e5b7cb0ab 100755
--- a/rawEx/fit/scatter2d/lmfit_with_plotting.py
+++ b/rawEx/fit/scatter2d/lmfit_with_plotting.py
@@ -25,16 +25,16 @@ class LMFITPlotter:
 
 
 if __name__ == '__main__':
-    real_data = model.create_real_data()
+    data = model.fake_data()
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(model.get_simulation, real_data, 1)
+    fit_objective.addFitPair(model.get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = lmfit.Parameters()
     P.add('radius', value=7*nm, min=5*nm, max=8*nm)
     P.add('length', value=10*nm, min=8*nm, max=14*nm)
-    
+
     <%= sm ? "" :"plotter = LMFITPlotter(fit_objective)" %>
     result = lmfit.minimize(fit_objective.evaluate_residuals,
                             P<%= sm ? "" :", iter_cb=plotter" %>)
diff --git a/rawEx/fit/scatter2d/minimizer_settings.py b/rawEx/fit/scatter2d/minimizer_settings.py
index c39b50349dea59a5692ec1cf8050bfb1a022b91f..48808a4725bb7535f7a249625808a6d3a065e84b 100755
--- a/rawEx/fit/scatter2d/minimizer_settings.py
+++ b/rawEx/fit/scatter2d/minimizer_settings.py
@@ -51,7 +51,7 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, get_sample(P), detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data from simulated image with default parameters.
     """
@@ -66,7 +66,7 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    return result.npArray()
+    return result
 
 
 def run_fitting():
@@ -74,7 +74,7 @@ def run_fitting():
     main function to run fitting
     """
 
-    real_data = create_real_data()
+    data = fake_data()
 
     # prints info about available minimizers
     print(ba.MinimizerFactory().catalogToString())
@@ -83,7 +83,7 @@ def run_fitting():
     print(ba.MinimizerFactory().catalogDetailsToString())
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(get_simulation, real_data, 1)
+    fit_objective.addFitPair(get_simulation, data, 1)
     fit_objective.initPrint(10)
 
     P = ba.Parameters()
diff --git a/rawEx/fit/scatter2d/model1_cylinders.py b/rawEx/fit/scatter2d/model1_cylinders.py
index a6d950027fa7b3d2ed21be576070d285c633f1ef..56d6d4e11bdc95d2d4cc7e0acb9ccb9a16ff37a8 100644
--- a/rawEx/fit/scatter2d/model1_cylinders.py
+++ b/rawEx/fit/scatter2d/model1_cylinders.py
@@ -42,19 +42,13 @@ def get_simulation(P):
     return ba.ScatteringSimulation(beam, sample, detector)
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
     P = {'radius': 5*nm, 'height': 10*nm}
 
-    # retrieving simulated data in the form of numpy array
     simulation = get_simulation(P)
     result = simulation.simulate()
-    real_data = result.npArray()
 
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
diff --git a/rawEx/fit/scatter2d/model2_hexlattice.py b/rawEx/fit/scatter2d/model2_hexlattice.py
index b29513ea87faa0e54b72b0aa1fb75ffa50df68f2..e6f82616e2a187b9a576e44b1f599de6230af5d2 100644
--- a/rawEx/fit/scatter2d/model2_hexlattice.py
+++ b/rawEx/fit/scatter2d/model2_hexlattice.py
@@ -48,7 +48,7 @@ def get_simulation(P):
     return simulation
 
 
-def create_real_data():
+def fake_data():
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -56,12 +56,4 @@ def create_real_data():
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with noise to produce "real" data
-    np.random.seed(0)
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
diff --git a/rawEx/fit/scatter2d/multiple_datasets.py b/rawEx/fit/scatter2d/multiple_datasets.py
index 0c2d8df452c7da8235667ca3a683e37d1457dc55..5edec05979f6e0406da6049f0c9f81f5c4528860 100755
--- a/rawEx/fit/scatter2d/multiple_datasets.py
+++ b/rawEx/fit/scatter2d/multiple_datasets.py
@@ -59,7 +59,7 @@ def simulation2(P):
     return get_simulation(P)
 
 
-def create_real_data(incident_alpha):
+def fake_data(incident_alpha):
     """
     Generating "real" data by adding noise to the simulated data.
     """
@@ -73,14 +73,7 @@ def create_real_data(incident_alpha):
     simulation = get_simulation(P)
     result = simulation.simulate()
 
-    # retrieving simulated data in the form of numpy array
-    real_data = result.npArray()
-
-    # spoiling simulated data with the noise to produce "real" data
-    noise_factor = 0.1
-    noisy = np.random.normal(real_data, noise_factor*np.sqrt(real_data))
-    noisy[noisy < 0.1] = 0.1
-    return noisy
+    return result.noisy(0.1, 0.1)
 
 
 class PlotObserver():
@@ -99,14 +92,14 @@ class PlotObserver():
     @staticmethod
     def plot_dataset(fit_objective, canvas):
         for i_dataset in range(0, fit_objective.fitObjectCount()):
-            real_data = fit_objective.experimentalData(i_dataset)
+            data = fit_objective.experimentalData(i_dataset)
             simul_data = fit_objective.simulationResult(i_dataset)
             chi2_map = fit_objective.relativeDifference(i_dataset)
 
-            zmax = real_data.maxVal()
+            zmax = data.maxVal()
 
             plt.subplot(canvas[i_dataset*3])
-            bp.plot_simres(real_data,
+            bp.plot_simres(data,
                              title="\"Real\" data - #" +
                              str(i_dataset + 1),
                              intensity_min=1,
@@ -189,12 +182,12 @@ def run_fitting():
     main function to run fitting
     """
 
-    data1 = create_real_data(0.1*deg)
-    data2 = create_real_data(0.4*deg)
+    data1 = fake_data(0.1*deg)
+    data2 = fake_data(0.4*deg)
 
     fit_objective = ba.FitObjective()
-    fit_objective.addSimulationAndData(simulation1, data1, 1)
-    fit_objective.addSimulationAndData(simulation2, data2, 1)
+    fit_objective.addFitPair(simulation1, data1, 1)
+    fit_objective.addFitPair(simulation2, data2, 1)
     fit_objective.initPrint(10)
 
     # creating custom observer which will draw fit progress
diff --git a/rawEx/fit/specular/Honeycomb_fit.py b/rawEx/fit/specular/Honeycomb_fit.py
index 39e0f863c6c9f170e387496c394f2f60963f8cff..bcb413ac4e6966ac1b85fb8d5a992da52d08157b 100755
--- a/rawEx/fit/specular/Honeycomb_fit.py
+++ b/rawEx/fit/specular/Honeycomb_fit.py
@@ -88,7 +88,7 @@ def run_simulation(qaxis, P, *, sign, T):
 #  Experimental data
 ####################################################################
 
-def get_experimental_data(filename, qmin, qmax):
+def load_data(filename, qmin, qmax):
     # Load from disk.
     filepath = os.path.join(datadir, filename)
     with open(filepath, 'r') as f:
@@ -217,11 +217,11 @@ if __name__ == '__main__':
     qmin = 0.08
     qmax = 1.4
 
-    expData = [
-        get_experimental_data("specular/honeycomb300p.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb300m.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb150p.dat", qmin, qmax),
-        get_experimental_data("specular/honeycomb150m.dat", qmin, qmax)]
+    data = [
+        load_data("specular/honeycomb300p.dat", qmin, qmax),
+        load_data("specular/honeycomb300m.dat", qmin, qmax),
+        load_data("specular/honeycomb150p.dat", qmin, qmax),
+        load_data("specular/honeycomb150m.dat", qmin, qmax)]
 
     simFunctions = [
         lambda q, P: run_simulation(q, P, sign=+1, T=300),
@@ -234,14 +234,14 @@ if __name__ == '__main__':
     # Plot data with initial model
 
     simResults = [ f(qzs, P) for f in simFunctions ]
-    plot(qzs, simResults, expData, [1, 1, 10, 10],
+    plot(qzs, simResults, data, [1, 1, 10, 10],
          ["300K $+$", "300K $-$", "150K $+$", "150K $-$"])
     plot_sld_profile(P)
 
     # Fit
 
-    qaxes = [d[0] for d in expData]
-    rdata = [d[1] for d in expData]
+    qaxes = [d[0] for d in data]
+    rdata = [d[1] for d in data]
 
     def par_dict(*args):
         return {name: value for name, value in zip(freeParNames, *args)} | fixedP
@@ -280,7 +280,7 @@ if __name__ == '__main__':
     P = par_dict(result.x)
 
     sim_results = [ f(qzs, P) for f in simFunctions ]
-    plot(qzs, simResults, expData, [1, 1, 10, 10],
+    plot(qzs, simResults, data, [1, 1, 10, 10],
          ["300K $+$", "300K $-$", "150K $+$", "150K $-$"])
     plot_sld_profile(P)
 
diff --git a/rawEx/fit/specular/PolarizedSpinAsymmetryFit.py b/rawEx/fit/specular/PolarizedSpinAsymmetryFit.py
index 782d35698dd97aa9ebb3a70e955f682baed42096..e10a8827c42083aedb8139073726a31919fb7593 100755
--- a/rawEx/fit/specular/PolarizedSpinAsymmetryFit.py
+++ b/rawEx/fit/specular/PolarizedSpinAsymmetryFit.py
@@ -32,36 +32,6 @@ def get_simulation(q_axis, fitParams, *, polarizer_dir, analyzer_dir):
 
     return simulation
 
-####################################################################
-#                          Fit Function                            #
-####################################################################
-
-def run_fit_ba(q_axis, r_data, r_uncertainty, simulationFactory,
-               startParams):
-
-    fit_objective = ba.FitObjective()
-    fit_objective.setObjectiveMetric("chi2")
-
-    fit_objective.addSimulationAndData(
-        lambda P: simulationFactory[0](q_axis[0], P), r_data[0],
-        r_uncertainty[0], 1)
-    fit_objective.addSimulationAndData(
-        lambda P: simulationFactory[1](q_axis[1], P), r_data[1],
-        r_uncertainty[1], 1)
-
-    fit_objective.initPrint(10)
-
-    P = ba.Parameters()
-    for name, p in startParams.items():
-        P.add(name, p[0], min=p[1], max=p[2])
-
-    minimizer = ba.Minimizer()
-
-    result = minimizer.minimize(fit_objective.evaluate, P)
-    fit_objective.finalize(result)
-
-    return {r.name(): r.value for r in result.parameters()}
-
 ####################################################################
 #                          Main Function                           #
 ####################################################################
@@ -71,8 +41,8 @@ if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
     fname_stem = os.path.join(datadir, "specular/MAFO_Saturated_")
 
-    expdata_pp = psa.load_exp(fname_stem + "pp.tab")
-    expdata_mm = psa.load_exp(fname_stem + "mm.tab")
+    data_pp = psa.load_data(fname_stem + "pp.tab")
+    data_mm = psa.load_data(fname_stem + "mm.tab")
 
     fixedParams = {
         # parameters can be moved here to keep them fixed
@@ -114,26 +84,43 @@ if __name__ == '__main__':
     legend_pp = "$++$"
     legend_mm = "$--$"
 
-    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [expdata_pp, expdata_mm],
+    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [data_pp, data_mm],
              [legend_pp, legend_mm], [color_pp, color_mm])
 
-    psa.plotSpinAsymmetry(expdata_pp, expdata_mm, qzs, r_pp, r_mm)
+    psa.plotSpinAsymmetry(data_pp, data_mm, qzs, r_pp, r_mm)
+
+    fit_objective = ba.FitObjective()
+    fit_objective.setObjectiveMetric("chi2")
+    fit_objective.initPrint(10)
+
+    fit_objective.addFitPair(
+        lambda P: get_Simulation_pp(data_pp.npXcenters(), P),
+        data_pp, 1)
+    fit_objective.addFitPair(
+        lambda P: get_Simulation_mm(data_mm.npXcenters(), P),
+        data_mm, 1)
+
+    P = ba.Parameters()
+    for name, p in startParams.items():
+        P.add(name, p[0], min=p[1], max=p[2])
+
+    minimizer = ba.Minimizer()
+
+    result = minimizer.minimize(fit_objective.evaluate, P)
+    fit_objective.finalize(result)
+
+    fitResult = {r.name(): r.value for r in result.parameters()}
 
-    fitResult = run_fit_ba([expdata_pp[0], expdata_mm[0]],
-                           [expdata_pp[1], expdata_mm[1]],
-                           [expdata_pp[2], expdata_mm[2]],
-                           [get_Simulation_pp, get_Simulation_mm],
-                           startParams)
     print("Fit Result:")
     print(fitResult)
 
     q_pp, r_pp = psa.qr(get_Simulation_pp(qzs, fitResult).simulate())
     q_mm, r_mm = psa.qr(get_Simulation_mm(qzs, fitResult).simulate())
 
-    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [expdata_pp, expdata_mm],
+    psa.plotData([q_pp, q_mm], [r_pp, r_mm], [data_pp, data_mm],
              [legend_pp, legend_mm], [color_pp, color_mm])
     <%= sm ? "#" : "" %>plt.draw()
 
-    psa.plotSpinAsymmetry(expdata_pp, expdata_mm, qzs, r_pp, r_mm)
+    psa.plotSpinAsymmetry(data_pp, data_mm, qzs, r_pp, r_mm)
     <%= sm ? "#" : "" %>plt.draw()
     <%= sm ? "#" : "" %>plt.show()
diff --git a/rawEx/fit/specular/Pt_layer_fit.py b/rawEx/fit/specular/Pt_layer_fit.py
index 17f91023a53e310fcf6ba83ca86323af5889388c..0d0b68d43839ac197d3c39e6b4e24ecd82905332 100755
--- a/rawEx/fit/specular/Pt_layer_fit.py
+++ b/rawEx/fit/specular/Pt_layer_fit.py
@@ -60,7 +60,7 @@ def get_simulation(q_axis, P):
 #  Experimental data
 ####################################################################
 
-def get_experimental_data(filename, qmin, qmax):
+def load_data(filename, qmin, qmax):
     filepath = os.path.join(datadir, filename)
     data = np.genfromtxt(filepath, unpack=True)
 
@@ -144,7 +144,7 @@ if __name__ == '__main__':
     qmin = 0.18
     qmax = 2.4
 
-    data = get_experimental_data("specular/RvsQ_36563_36662.dat.gz", qmin, qmax)
+    data = load_data("specular/RvsQ_36563_36662.dat.gz", qmin, qmax)
 
     qzs = np.linspace(qmin, qmax, 1500)
 
diff --git a/rawEx/specular/PolarizedSpinAsymmetry.py b/rawEx/specular/PolarizedSpinAsymmetry.py
index 15ae40211155a59284e76404ee97d1af7b7d289a..c3f6c0b2e36d74fc0a47df99622c1771efc57538 100755
--- a/rawEx/specular/PolarizedSpinAsymmetry.py
+++ b/rawEx/specular/PolarizedSpinAsymmetry.py
@@ -118,10 +118,10 @@ def plotData(qs, rs, exps, labels, colors):
     ax = fig.add_subplot(111)
 
     for q, r, exp, l, c in zip(qs, rs, exps, labels, colors):
-        ax.errorbar(exp[0],
-                    exp[1],
-                    xerr=exp[3],
-                    yerr=exp[2],
+        ax.errorbar(exp.xAxis().binCenters(),
+                    exp.flatVector(),
+                    # xerr=TODO i742,
+                    yerr=exp.errorSigmas(),
                     fmt='.',
                     markersize=0.75,
                     linewidth=0.5,
@@ -142,17 +142,19 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
     Plot the simulated spin asymmetry as well its
     experimental counterpart with errorbars
     """
+    Yp = data_pp.npArray()
+    Ym = data_mm.npArray()
+    Ep = data_pp.npErrors()
+    Em = data_mm.npErrors()
     # compute the errorbars of the spin asymmetry
-    delta = numpy.sqrt(4 * (data_pp[1]**2 * data_mm[2]**2 + \
-            data_mm[1]**2 * data_pp[2]**2 ) /
-                ( data_pp[1] + data_mm[1] )**4 )
+    delta = numpy.sqrt(4 * (Yp**2 * Em**2 + Ym**2 * Ep**2 ) / ( Yp + Ym )**4 )
 
     fig = plt.figure()
     ax = fig.add_subplot(111)
 
-    ax.errorbar(data_pp[0],
-                (data_pp[1] - data_mm[1])/(data_pp[1] + data_mm[1]),
-                xerr=data_pp[3],
+    ax.errorbar(data_pp.xAxis().binCenters(),
+                (Yp - Ym) / (Yp + Ym),
+                # xerr=TODO i742,
                 yerr=delta,
                 fmt='.',
                 markersize=0.75,
@@ -172,9 +174,9 @@ def plotSpinAsymmetry(data_pp, data_mm, q, r_pp, r_mm):
 #                          Data Handling                           #
 ####################################################################
 
-def load_exp(fname):
-    dat = numpy.loadtxt(fname)
-    return numpy.transpose(dat)
+def load_data(fname):
+    flags = ba.ImportSettings1D("#", "", 1, 2, 3, 4)
+    return ba.readData1D(fname, ba.csv1D, flags)
 
 ####################################################################
 #                          Main Function                           #
@@ -184,8 +186,8 @@ if __name__ == '__main__':
     datadir = os.getenv('BA_DATA_DIR', '')
     fname_stem = os.path.join(datadir, "specular/MAFO_Saturated_")
 
-    expdata_pp = load_exp(fname_stem + "pp.tab")
-    expdata_mm = load_exp(fname_stem + "mm.tab")
+    expdata_pp = load_data(fname_stem + "pp.tab")
+    expdata_mm = load_data(fname_stem + "mm.tab")
 
     fixedP = {
         # parameters from our own fit run
diff --git a/rawEx/specular/VsGenx.py b/rawEx/specular/VsGenx.py
index 8150c11d15f18bdbd357dc5f3db0a00c5e7bfcb4..dbd5a59f3604eda8f97083de288a0d2102e0e40b 100755
--- a/rawEx/specular/VsGenx.py
+++ b/rawEx/specular/VsGenx.py
@@ -16,7 +16,7 @@ def reference_data(filename):
     """
     Loads and returns reference data from GenX simulation
     """
-    ax_values, real_data = np.loadtxt(filename,
+    ax_values, data = np.loadtxt(filename,
                                       usecols=(0, 1),
                                       skiprows=3,
                                       unpack=True)
@@ -24,7 +24,7 @@ def reference_data(filename):
     # translate axis values from double incident angle to incident angle
     ax_values *= 0.5
 
-    return ax_values, real_data
+    return ax_values, data
 
 
 def get_sample():