diff --git a/Base/Axis/DiscreteAxis.cpp b/Base/Axis/DiscreteAxis.cpp
index 75d42a843a3f0a0fa0d52480524c4906c7f2c9c2..83286bf9e6e508c3fd1c6b9afdc651b70caa87a2 100644
--- a/Base/Axis/DiscreteAxis.cpp
+++ b/Base/Axis/DiscreteAxis.cpp
@@ -75,3 +75,8 @@ void DiscreteAxis::sanityCheck() const
         throw std::runtime_error("Error in DiscreteAxis::DiscreteAxis: passed coordinate vector "
                                  "contains repeating values");
 }
+
+IAxis* newDiscreteAxis(const std::string& name, const std::vector<double>& points)
+{
+    return new IAxis(name, centers2bins(points));
+}
diff --git a/Base/Axis/DiscreteAxis.h b/Base/Axis/DiscreteAxis.h
index 896fbfbe5c867c38c56e09536ec2aa7fd0d8fdfb..27a7d16b8798224d6ee0c9b8ef97c2863a8a743c 100644
--- a/Base/Axis/DiscreteAxis.h
+++ b/Base/Axis/DiscreteAxis.h
@@ -31,4 +31,6 @@ private:
     std::vector<double> m_coordinates;
 };
 
+IAxis* newDiscreteAxis(const std::string& name, const std::vector<double>& points);
+
 #endif // BORNAGAIN_BASE_AXIS_DISCRETEAXIS_H
diff --git a/Device/Coord/CoordSystem1D.cpp b/Device/Coord/CoordSystem1D.cpp
index b51374dd5833a5151ff617e0678e867582793d1d..9056a808b3ce06a9e1c40753538b3af6eb1f1b19 100644
--- a/Device/Coord/CoordSystem1D.cpp
+++ b/Device/Coord/CoordSystem1D.cpp
@@ -49,14 +49,13 @@ double backTransform(double value, Coords coords, double wavelength)
     }
 }
 
-DiscreteAxis* createAxisFrom(const IAxis& axis, Coords coords, const std::string& name,
-                             double wavelength)
+IAxis* createAxisFrom(const IAxis& axis, Coords coords, const std::string& name, double wavelength)
 {
     std::vector<double> ret;
     ret.reserve(axis.size());
     for (const double value : axis.binCenters())
         ret.emplace_back(backTransform(value, coords, wavelength));
-    return new DiscreteAxis(name, ret);
+    return newDiscreteAxis(name, ret);
 }
 
 } // namespace
@@ -103,7 +102,7 @@ IAxis* CoordSystem1D::convertedAxis(size_t i_axis, Coords units) const
     auto coords = m_axes[0]->binCenters();
     for (size_t i = 0, size = coords.size(); i < size; ++i)
         coords[i] = translator(coords[i]);
-    return new DiscreteAxis(nameOfAxis(0, units), coords);
+    return newDiscreteAxis(nameOfAxis(0, units), coords);
 }
 
 
diff --git a/Device/IO/DataFormatUtil.cpp b/Device/IO/DataFormatUtil.cpp
index 81a06ab80bdfaf578382ea2aca4a700ec7de8e88..64e3e6dc3bb60b8250e61047cd2627f698860351 100644
--- a/Device/IO/DataFormatUtil.cpp
+++ b/Device/IO/DataFormatUtil.cpp
@@ -112,7 +112,7 @@ IAxis* DataUtil::Format::createAxis(std::istream& input_stream)
             throw std::runtime_error("Reading DiscreteAxis: cannot read name");
         std::vector<double> coordinates;
         DataUtil::Format::readLineOfDoubles(coordinates, iss);
-        return new DiscreteAxis(name, coordinates);
+        return newDiscreteAxis(name, coordinates);
 
     } else
         throw std::runtime_error("Unknown axis type '" + type + "'");
diff --git a/Device/IO/ReadReflectometry.cpp b/Device/IO/ReadReflectometry.cpp
index 6c2326a1322218ea8f7bf4d4df8b12cfb3489894..fe679d3cf05be12214462425593f5a07ff683c5f 100644
--- a/Device/IO/ReadReflectometry.cpp
+++ b/Device/IO/ReadReflectometry.cpp
@@ -93,5 +93,5 @@ Datafield* ReadReflectometry::readDatafield(std::istream& inStream)
             continue;
         eVec.push_back(it->second);
     }
-    return new Datafield{{new DiscreteAxis("qVector", qVec)}, rVec, eVec};
+    return new Datafield{{newDiscreteAxis("qVector", qVec)}, rVec, eVec};
 }
diff --git a/GUI/View/Loaders/QREDataLoader.cpp b/GUI/View/Loaders/QREDataLoader.cpp
index a6c803ac21d519ad8e47404173f04be7278258f1..0ded6fe4ef144414402497ef317b5f03cc6a869c 100644
--- a/GUI/View/Loaders/QREDataLoader.cpp
+++ b/GUI/View/Loaders/QREDataLoader.cpp
@@ -559,7 +559,7 @@ void QREDataLoader::datafieldFromParsingResult(RealItem* item) const
             eVec.push_back(m_importResult.eValues[lineNr]);
 
     auto* oData =
-        new Datafield({new DiscreteAxis("qVector", qVec)}, rVec, eVec); // eVec can be empty
+        new Datafield({newDiscreteAxis("qVector", qVec)}, rVec, eVec); // eVec can be empty
 
     // -- Replacement of item->setImportData(std::move(data));
     item->initNativeData();
diff --git a/Sim/Scan/QzScan.cpp b/Sim/Scan/QzScan.cpp
index 5a5ab4961cc066ad8ca200441893a8e2af2fb074..1ce6f020a04d7ee047ace640579b8d803bf7c081 100644
--- a/Sim/Scan/QzScan.cpp
+++ b/Sim/Scan/QzScan.cpp
@@ -37,7 +37,7 @@ QzScan::QzScan(IAxis* qs_nm)
 }
 
 QzScan::QzScan(std::vector<double> qs_nm)
-    : QzScan(new DiscreteAxis("qs", std::move(qs_nm)))
+    : QzScan(newDiscreteAxis("qs", std::move(qs_nm)))
 {
 }
 
diff --git a/Tests/Unit/GUI/TestRealModel.cpp b/Tests/Unit/GUI/TestRealModel.cpp
index 901da6316c95634709e2d6a4330804fd4f15a38a..3cb514034f8e4b2492926086e34ecf6d2d23ca74 100644
--- a/Tests/Unit/GUI/TestRealModel.cpp
+++ b/Tests/Unit/GUI/TestRealModel.cpp
@@ -81,7 +81,7 @@ TEST(TestRealModel, removeNativeData)
     EXPECT_EQ(item->nativeDataItem()->c_field(), nullptr);
 
     // set datafield
-    auto* oData = new Datafield{{new DiscreteAxis("qVector", std::vector<double>({1, 2}))}};
+    auto* oData = new Datafield{{newDiscreteAxis("qVector", std::vector<double>({1, 2}))}};
     oData->setVector(std::vector<double>({3, 4}));
     item->nativeDataItem()->setDatafield(oData); // takes ownership of odata
     EXPECT_TRUE(item->hasNativeData());
diff --git a/auto/Wrap/libBornAgainBase.py b/auto/Wrap/libBornAgainBase.py
index 011f5681db1f24a0730ca9f149571d061c431664..35cb2f6aea2181323531a5f1359ca39b1b29efd5 100644
--- a/auto/Wrap/libBornAgainBase.py
+++ b/auto/Wrap/libBornAgainBase.py
@@ -1969,6 +1969,10 @@ class DiscreteAxis(IAxis):
 
 # Register DiscreteAxis in _libBornAgainBase:
 _libBornAgainBase.DiscreteAxis_swigregister(DiscreteAxis)
+
+def newDiscreteAxis(name, points):
+    r"""newDiscreteAxis(std::string const & name, vdouble1d_t points) -> IAxis"""
+    return _libBornAgainBase.newDiscreteAxis(name, points)
 class Frame(ICloneable):
     r"""Proxy of C++ Frame class."""
 
diff --git a/auto/Wrap/libBornAgainBase_wrap.cpp b/auto/Wrap/libBornAgainBase_wrap.cpp
index ffa44f63c11f04c21286ae8876aa7f1f7d6db9b8..b1e30dcdf7bf91cd7eb65e0bd8634a7dca3f8f25 100644
--- a/auto/Wrap/libBornAgainBase_wrap.cpp
+++ b/auto/Wrap/libBornAgainBase_wrap.cpp
@@ -26263,6 +26263,50 @@ SWIGINTERN PyObject *DiscreteAxis_swiginit(PyObject *SWIGUNUSEDPARM(self), PyObj
   return SWIG_Python_InitShadowInstance(args);
 }
 
+SWIGINTERN PyObject *_wrap_newDiscreteAxis(PyObject *self, PyObject *args) {
+  PyObject *resultobj = 0;
+  std::string *arg1 = 0 ;
+  std::vector< double,std::allocator< double > > *arg2 = 0 ;
+  int res1 = SWIG_OLDOBJ ;
+  int res2 = SWIG_OLDOBJ ;
+  PyObject *swig_obj[2] ;
+  IAxis *result = 0 ;
+  
+  if (!SWIG_Python_UnpackTuple(args, "newDiscreteAxis", 2, 2, swig_obj)) SWIG_fail;
+  {
+    std::string *ptr = (std::string *)0;
+    res1 = SWIG_AsPtr_std_string(swig_obj[0], &ptr);
+    if (!SWIG_IsOK(res1)) {
+      SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "newDiscreteAxis" "', argument " "1"" of type '" "std::string const &""'"); 
+    }
+    if (!ptr) {
+      SWIG_exception_fail(SWIG_ValueError, "invalid null reference " "in method '" "newDiscreteAxis" "', argument " "1"" of type '" "std::string const &""'"); 
+    }
+    arg1 = ptr;
+  }
+  {
+    std::vector< double,std::allocator< double > > *ptr = (std::vector< double,std::allocator< double > > *)0;
+    res2 = swig::asptr(swig_obj[1], &ptr);
+    if (!SWIG_IsOK(res2)) {
+      SWIG_exception_fail(SWIG_ArgError(res2), "in method '" "newDiscreteAxis" "', argument " "2"" of type '" "std::vector< double,std::allocator< double > > const &""'"); 
+    }
+    if (!ptr) {
+      SWIG_exception_fail(SWIG_ValueError, "invalid null reference " "in method '" "newDiscreteAxis" "', argument " "2"" of type '" "std::vector< double,std::allocator< double > > const &""'"); 
+    }
+    arg2 = ptr;
+  }
+  result = (IAxis *)newDiscreteAxis((std::string const &)*arg1,(std::vector< double,std::allocator< double > > const &)*arg2);
+  resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_IAxis, 0 |  0 );
+  if (SWIG_IsNewObj(res1)) delete arg1;
+  if (SWIG_IsNewObj(res2)) delete arg2;
+  return resultobj;
+fail:
+  if (SWIG_IsNewObj(res1)) delete arg1;
+  if (SWIG_IsNewObj(res2)) delete arg2;
+  return NULL;
+}
+
+
 SWIGINTERN PyObject *_wrap_new_Frame(PyObject *self, PyObject *args) {
   PyObject *resultobj = 0;
   std::vector< IAxis const *,std::allocator< IAxis const * > > *arg1 = 0 ;
@@ -28754,6 +28798,7 @@ static PyMethodDef SwigMethods[] = {
 	 { "delete_DiscreteAxis", _wrap_delete_DiscreteAxis, METH_O, "delete_DiscreteAxis(DiscreteAxis self)"},
 	 { "DiscreteAxis_swigregister", DiscreteAxis_swigregister, METH_O, NULL},
 	 { "DiscreteAxis_swiginit", DiscreteAxis_swiginit, METH_VARARGS, NULL},
+	 { "newDiscreteAxis", _wrap_newDiscreteAxis, METH_VARARGS, "newDiscreteAxis(std::string const & name, vdouble1d_t points) -> IAxis"},
 	 { "new_Frame", _wrap_new_Frame, METH_O, "new_Frame(std::vector< IAxis const *,std::allocator< IAxis const * > > && axes) -> Frame"},
 	 { "delete_Frame", _wrap_delete_Frame, METH_O, "delete_Frame(Frame self)"},
 	 { "Frame_clone", _wrap_Frame_clone, METH_O, "Frame_clone(Frame self) -> Frame"},