diff --git a/Framework/DataObjects/CMakeLists.txt b/Framework/DataObjects/CMakeLists.txt
index 45c84d1d3a99b1400cb426c26a27371a71db490a..aa8f3ad56e5d7cbc622227fc60da59d079a3ce36 100644
--- a/Framework/DataObjects/CMakeLists.txt
+++ b/Framework/DataObjects/CMakeLists.txt
@@ -31,7 +31,6 @@ set(SRC_FILES
     src/LeanElasticPeak.cpp
     src/BasePeak.cpp
     src/PeakColumn.cpp
-    src/LeanElasticPeakColumn.cpp
     src/PeakNoShapeFactory.cpp
     src/PeakShapeBase.cpp
     src/PeakShapeEllipsoid.cpp
@@ -115,7 +114,6 @@ set(INC_FILES
     inc/MantidDataObjects/LeanElasticPeak.h
     inc/MantidDataObjects/BasePeak.h
     inc/MantidDataObjects/PeakColumn.h
-    inc/MantidDataObjects/LeanElasticPeakColumn.h
     inc/MantidDataObjects/PeakNoShapeFactory.h
     inc/MantidDataObjects/PeakShapeBase.h
     inc/MantidDataObjects/PeakShapeEllipsoid.h
@@ -177,7 +175,6 @@ set(TEST_FILES
     NoShapeTest.h
     OffsetsWorkspaceTest.h
     PeakColumnTest.h
-    LeanElasticPeakColumnTest.h
     PeakNoShapeFactoryTest.h
     PeakShapeEllipsoidFactoryTest.h
     PeakShapeEllipsoidTest.h
diff --git a/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h b/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
index 0f5df6a8820091aeb95c5da1d8eaec3abaf7018f..ac3aa85b097a8832cc116c206f7f462209cc0fd1 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/BasePeak.h
@@ -32,9 +32,6 @@ namespace DataObjects {
  */
 class DLLExport BasePeak : public Geometry::IPeak {
 public:
-  /// Allow PeakColumn class to directly access members.
-  friend class PeakColumn;
-
   BasePeak();
   BasePeak(const Mantid::Kernel::Matrix<double> &goniometer);
   /// Copy constructor
diff --git a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
index 527e3c52c4634d80dc0003d4c13c4f375cc815a3..67a3b7e49c0e13a4a3a999bd2b497d80d062ebab 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeak.h
@@ -30,8 +30,6 @@ namespace DataObjects {
  */
 class DLLExport LeanElasticPeak : public BasePeak {
 public:
-  /// Allow PeakColumn class to directly access members.
-  friend class PeakColumn;
 
   LeanElasticPeak();
   LeanElasticPeak(const Mantid::Kernel::V3D &QSampleFrame);
diff --git a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeakColumn.h b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeakColumn.h
deleted file mode 100644
index eca3415d85bccad3465bcb40fe8d13798af0e8f6..0000000000000000000000000000000000000000
--- a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeakColumn.h
+++ /dev/null
@@ -1,107 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-//   NScD Oak Ridge National Laboratory, European Spallation Source,
-//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
-// SPDX - License - Identifier: GPL - 3.0 +
-#pragma once
-
-#include "MantidAPI/Column.h"
-#include "MantidDataObjects/LeanElasticPeak.h"
-
-#include <boost/variant.hpp>
-#include <list>
-
-namespace Mantid {
-namespace DataObjects {
-
-/** PeakColumn : a Column sub-class used to display
- * peak information as a TableWorkspace.
- *
- * The column holds a reference to a vector of Peak objects.
- * Values in the column are taken directly from those Peak objects.
- *
- * @author Janik Zikovsky
- * @date 2011-04-25 18:06:32.952258
- */
-class DLLExport LeanElasticPeakColumn : public Mantid::API::Column {
-
-public:
-  /// Construct a column with a reference to the peaks list, a name & type
-  LeanElasticPeakColumn(std::vector<LeanElasticPeak> &peaks,
-                        const std::string &name);
-
-  /// Number of individual elements in the column.
-  size_t size() const override { return m_peaks.size(); }
-
-  /// Returns typeid for the data in the column
-  const std::type_info &get_type_info() const override;
-
-  /// Returns typeid for the pointer type to the data element in the column
-  const std::type_info &get_pointer_type_info() const override;
-
-  bool getReadOnly() const override;
-
-  /// Prints
-  void print(size_t index, std::ostream &s) const override;
-
-  void read(size_t index, const std::string &text) override;
-
-  /// Sets item from a stream
-  void read(const size_t index, std::istringstream &in) override;
-
-  /// Specialized type check
-  bool isBool() const override;
-
-  bool isNumber() const override;
-
-  /// Must return overall memory size taken by the column.
-  long int sizeOfData() const override;
-
-  /// Clone.
-  LeanElasticPeakColumn *clone() const override;
-
-  /// Cast to double
-  double toDouble(size_t i) const override;
-
-  /// Assign from double
-  void fromDouble(size_t i, double value) override;
-
-  /// Reference to the data.
-  const std::vector<LeanElasticPeak> &data() const { return m_peaks; }
-
-  bool equals(const Column &otherColumn, double tolerance) const override {
-    (void)otherColumn;
-    (void)tolerance;
-    throw std::runtime_error(
-        "equals not implemented, to compare use CompareWorkspace");
-  }
-
-protected:
-  /// Sets the new column size.
-  void resize(size_t count) override;
-  /// Inserts an item.
-  void insert(size_t index) override;
-  /// Removes an item.
-  void remove(size_t index) override;
-  /// Pointer to a data element
-  void *void_pointer(size_t index) override;
-  /// Pointer to a data element
-  const void *void_pointer(size_t index) const override;
-
-private:
-  /// Reference to the peaks object saved in the PeaksWorkspace.
-  std::vector<LeanElasticPeak> &m_peaks;
-  /// Precision of hkl in table workspace
-  int m_hklPrec;
-
-  /// Type of the row cache value
-  using CacheValueType = boost::variant<double, int, std::string, Kernel::V3D>;
-  ///
-  mutable std::list<CacheValueType> m_oldRows;
-  /// Sets the correct value in the referenced peak.
-  void setPeakHKLOrRunNumber(const size_t index, const double val);
-};
-
-} // namespace DataObjects
-} // namespace Mantid
diff --git a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeaksWorkspace.h b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeaksWorkspace.h
index e2f671536441855ac5b736951b55b650e8b889df..0d5783a5bafcd99c523ad7b7c8a105a363a0beb6 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeaksWorkspace.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/LeanElasticPeaksWorkspace.h
@@ -10,7 +10,7 @@
 #include "MantidAPI/ITableWorkspace.h"
 #include "MantidDataObjects/DllConfig.h"
 #include "MantidDataObjects/LeanElasticPeak.h"
-#include "MantidDataObjects/LeanElasticPeakColumn.h"
+#include "MantidDataObjects/PeakColumn.h"
 #include "MantidGeometry/Crystal/IPeak.h"
 #include "MantidKernel/SpecialCoordinateSystem.h"
 #include "MantidKernel/V3D.h"
@@ -277,7 +277,7 @@ private:
   std::vector<LeanElasticPeak> peaks;
 
   /** Column shared pointers. */
-  std::vector<std::shared_ptr<Mantid::DataObjects::LeanElasticPeakColumn>>
+  std::vector<std::shared_ptr<Mantid::DataObjects::PeakColumn<LeanElasticPeak>>>
       columns;
 
   /** Column names */
diff --git a/Framework/DataObjects/inc/MantidDataObjects/Peak.h b/Framework/DataObjects/inc/MantidDataObjects/Peak.h
index c8572fc9f1fd39a3168a0b5c4a8d47f6b23dbd62..11ee02932b37662bb7b031966e4b394fdf81e712 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/Peak.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/Peak.h
@@ -34,9 +34,6 @@ namespace DataObjects {
  */
 class DLLExport Peak : public BasePeak {
 public:
-  /// Allow PeakColumn class to directly access members.
-  friend class PeakColumn;
-
   Peak();
   Peak(const Geometry::Instrument_const_sptr &m_inst,
        const Mantid::Kernel::V3D &QLabFrame,
diff --git a/Framework/DataObjects/inc/MantidDataObjects/PeakColumn.h b/Framework/DataObjects/inc/MantidDataObjects/PeakColumn.h
index c9e4024f8b9b90527ebc35926faba7db73bd2eb0..9e09910ac30c49ea6dc32f2c0e980ac87c9fa292 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/PeakColumn.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/PeakColumn.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include "MantidAPI/Column.h"
+#include "MantidDataObjects/LeanElasticPeak.h"
 #include "MantidDataObjects/Peak.h"
 
 #include <boost/variant.hpp>
@@ -24,11 +25,11 @@ namespace DataObjects {
  * @author Janik Zikovsky
  * @date 2011-04-25 18:06:32.952258
  */
-class DLLExport PeakColumn : public Mantid::API::Column {
+template <class T> class DLLExport PeakColumn : public Mantid::API::Column {
 
 public:
   /// Construct a column with a reference to the peaks list, a name & type
-  PeakColumn(std::vector<Peak> &peaks, const std::string &name);
+  PeakColumn(std::vector<T> &peaks, const std::string &name);
 
   /// Number of individual elements in the column.
   size_t size() const override { return m_peaks.size(); }
@@ -67,7 +68,7 @@ public:
   void fromDouble(size_t i, double value) override;
 
   /// Reference to the data.
-  const std::vector<Peak> &data() const { return m_peaks; }
+  const std::vector<T> &data() const { return m_peaks; }
 
   bool equals(const Column &otherColumn, double tolerance) const override {
     (void)otherColumn;
@@ -90,7 +91,7 @@ protected:
 
 private:
   /// Reference to the peaks object saved in the PeaksWorkspace.
-  std::vector<Peak> &m_peaks;
+  std::vector<T> &m_peaks;
   /// Precision of hkl in table workspace
   int m_hklPrec;
 
diff --git a/Framework/DataObjects/inc/MantidDataObjects/PeaksWorkspace.h b/Framework/DataObjects/inc/MantidDataObjects/PeaksWorkspace.h
index a06284d2fa48a48ce246529ea963f17572be103f..4f64f09323eea36a270ff0e0af09188744dfcc6f 100644
--- a/Framework/DataObjects/inc/MantidDataObjects/PeaksWorkspace.h
+++ b/Framework/DataObjects/inc/MantidDataObjects/PeaksWorkspace.h
@@ -269,7 +269,7 @@ private:
   std::vector<Peak> peaks;
 
   /** Column shared pointers. */
-  std::vector<std::shared_ptr<Mantid::DataObjects::PeakColumn>> columns;
+  std::vector<std::shared_ptr<Mantid::DataObjects::PeakColumn<Peak>>> columns;
 
   /** Column names */
   std::vector<std::string> columnNames;
diff --git a/Framework/DataObjects/src/LeanElasticPeakColumn.cpp b/Framework/DataObjects/src/LeanElasticPeakColumn.cpp
deleted file mode 100644
index 48ae42a5b999984a5e5a990d85e1376b980d40c1..0000000000000000000000000000000000000000
--- a/Framework/DataObjects/src/LeanElasticPeakColumn.cpp
+++ /dev/null
@@ -1,369 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-//   NScD Oak Ridge National Laboratory, European Spallation Source,
-//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
-// SPDX - License - Identifier: GPL - 3.0 +
-#include "MantidDataObjects/LeanElasticPeakColumn.h"
-#include "MantidKernel/ConfigService.h"
-#include "MantidKernel/Exception.h"
-#include "MantidKernel/MultiThreaded.h"
-#include "MantidKernel/Strings.h"
-#include "MantidKernel/System.h"
-
-#include <boost/variant/get.hpp>
-
-using namespace Mantid::Kernel;
-
-namespace Mantid {
-namespace DataObjects {
-namespace {
-/// static logger
-Kernel::Logger g_log("LeanElasticPeakColumn");
-
-/// Number of items to keep around in the cell cache (see void_pointer())
-size_t NCELL_ITEM_CACHED = 100;
-
-/**
- * Private implementation to wrap a map such that it can be
- * initialized in a thread-safe manner with a local static
- */
-class ColumnNameToType {
-public:
-  ColumnNameToType() {
-    // Assume double if not in this map
-    m_type_index.emplace("DetID", "int");
-    m_type_index.emplace("RunNumber", "int");
-    m_type_index.emplace("h", "double");
-    m_type_index.emplace("k", "double");
-    m_type_index.emplace("l", "double");
-    m_type_index.emplace("Wavelength", "double");
-    m_type_index.emplace("Energy", "double");
-    m_type_index.emplace("TOF", "double");
-    m_type_index.emplace("DSpacing", "double");
-    m_type_index.emplace("Intens", "double");
-    m_type_index.emplace("SigInt", "double");
-    m_type_index.emplace("Intens/SigInt", "double");
-    m_type_index.emplace("BinCount", "double");
-    m_type_index.emplace("BankName", "str");
-    m_type_index.emplace("Row", "double");
-    m_type_index.emplace("Col", "double");
-    m_type_index.emplace("QLab", "V3D");
-    m_type_index.emplace("QSample", "V3D");
-    m_type_index.emplace("PeakNumber", "int");
-    m_type_index.emplace("TBar", "double");
-  }
-
-  inline const auto &data() const { return m_type_index; }
-
-private:
-  std::unordered_map<std::string, std::string> m_type_index;
-};
-
-/**
- * Returns a string type identifier from the given name
- * @param name :: The name of the column
- * @returns A string identifier for the column type
- */
-const std::string typeFromName(const std::string &name) {
-  static ColumnNameToType typeIndex;
-  auto iter = typeIndex.data().find(name);
-  if (iter != typeIndex.data().end()) {
-    return iter->second;
-  } else {
-    throw std::runtime_error("LeanElasticPeakColumn - Unknown column name: \"" +
-                             name +
-                             "\""
-                             "Peak column names/types must be explicitly "
-                             "marked in LeanElasticPeakColumn.cpp");
-  }
-}
-} // namespace
-
-//----------------------------------------------------------------------------------------------
-/** Constructor
- * @param peaks :: vector of peaks
- * @param name :: name for the column
- */
-LeanElasticPeakColumn::LeanElasticPeakColumn(
-    std::vector<LeanElasticPeak> &peaks, const std::string &name)
-    : m_peaks(peaks), m_oldRows() {
-  this->m_name = name;
-  this->m_type = typeFromName(name); // Throws if the name is unknown
-  const std::string key = "PeakColumn.hklPrec";
-  auto hklPrec = ConfigService::Instance().getValue<int>(key);
-  this->m_hklPrec = hklPrec.get_value_or(2);
-  if (!hklPrec.is_initialized()) {
-    g_log.information()
-        << "In LeanElasticPeakColumn constructor, did not find any value for '"
-        << key
-        << "' from the Config Service. Using default: " << this->m_hklPrec
-        << "\n";
-  }
-}
-
-/// Returns typeid for the data in the column
-const std::type_info &LeanElasticPeakColumn::get_type_info() const {
-  // This is horrible copy-and-paste with the method below. The whole thing
-  // around columns could be much better implemented using templates & traits to
-  // avoid this
-  // type of thing!
-
-  if (type() == "double") {
-    return typeid(double);
-  } else if (type() == "int") {
-    return typeid(int);
-  } else if (type() == "str") {
-    return typeid(std::string);
-  } else if (type() == "V3D") {
-    return typeid(V3D);
-  } else {
-    throw std::runtime_error(
-        "LeanElasticPeakColumn::get_type_info() - Unknown column type: " +
-        m_name);
-  }
-}
-
-/// Returns typeid for the pointer type to the data element in the column
-const std::type_info &LeanElasticPeakColumn::get_pointer_type_info() const {
-  if (type() == "double") {
-    return typeid(double *);
-  } else if (type() == "int") {
-    return typeid(int *);
-  } else if (type() == "str") {
-    return typeid(std::string *);
-  } else if (type() == "V3D") {
-    return typeid(V3D *);
-  } else {
-    throw std::runtime_error(
-        "LeanElasticPeakColumn::get_pointer_type_info() -: " + m_name);
-  }
-}
-
-//-------------------------------------------------------------------------------------
-/** Prints out the column string at the given row index.
- *
- * @param s :: stream to output
- * @param index :: row index
- */
-void LeanElasticPeakColumn::print(size_t index, std::ostream &s) const {
-  LeanElasticPeak &peak = m_peaks[index];
-  s.imbue(std::locale("C"));
-  std::ios::fmtflags fflags(s.flags());
-  if (m_name == "RunNumber")
-    s << peak.getRunNumber();
-  else if (m_name == "DetID")
-    s << peak.getDetectorID();
-  else if (m_name == "BankName")
-    s << peak.getBankName();
-  else if (m_name == "QLab")
-    s << peak.getQLabFrame();
-  else if (m_name == "QSample")
-    s << peak.getQSampleFrame();
-  else if (m_name == "h") {
-    s << std::fixed << std::setprecision(m_hklPrec) << peak.getH();
-  } else if (m_name == "k") {
-    s << std::fixed << std::setprecision(m_hklPrec) << peak.getK();
-  } else if (m_name == "l") {
-    s << std::fixed << std::setprecision(m_hklPrec) << peak.getL();
-  } else if (m_name == "PeakNumber") {
-    s << peak.getPeakNumber();
-  } else
-    s << peak.getValueByColName(m_name);
-  s.flags(fflags);
-}
-
-//-------------------------------------------------------------------------------------
-/** Read in some text and convert to a number in the PeaksWorkspace
- *
- * @param text :: string to read
- * @param index :: index of the peak to modify
- */
-void LeanElasticPeakColumn::read(size_t index, const std::string &text) {
-  // Don't modify read-only ones
-  if (this->getReadOnly() || index >= m_peaks.size())
-    return;
-
-  // Convert to a double
-  double val = 0;
-  int success = Strings::convert(text, val);
-
-  if (success == 0) {
-    g_log.error() << "Could not convert string '" << text << "' to a number.\n";
-    return;
-  }
-  setPeakHKLOrRunNumber(index, val);
-}
-
-/** Read in from stream and convert to a number in the PeaksWorkspace
- *
- * @param index :: index of the peak to modify
- * @param in :: input stream
- */
-void LeanElasticPeakColumn::read(const size_t index, std::istringstream &in) {
-  if (this->getReadOnly() || index >= m_peaks.size())
-    return;
-
-  double val;
-  try {
-    in >> val;
-  } catch (std::exception &e) {
-    g_log.error() << "Could not convert input to a number. " << e.what()
-                  << '\n';
-    return;
-  }
-
-  setPeakHKLOrRunNumber(index, val);
-}
-
-//-------------------------------------------------------------------------------------
-/** @return true if the column is read-only */
-bool LeanElasticPeakColumn::getReadOnly() const {
-  return !((m_name == "h") || (m_name == "k") || (m_name == "l") ||
-           (m_name == "RunNumber"));
-}
-
-//-------------------------------------------------------------------------------------
-/// Specialized type check
-bool LeanElasticPeakColumn::isBool() const { return false; }
-
-bool LeanElasticPeakColumn::isNumber() const { return false; }
-
-/// @returns overall memory size taken by the column.
-long int LeanElasticPeakColumn::sizeOfData() const {
-  return sizeof(double) * static_cast<long int>(m_peaks.size());
-}
-
-/**
- * Sets a new size for the column. Not implemented as this is controlled
- * by the PeaksWorkspace
- * @param count :: Count of new column size (unused)
- * @throw Exception::NotImplementedError
- */
-void LeanElasticPeakColumn::resize(size_t count) {
-  UNUSED_ARG(count);
-  throw Exception::NotImplementedError(
-      "LeanElasticPeakColumn::resize - Peaks must be "
-      "added through the PeaksWorkspace "
-      "interface.");
-}
-
-/**
- * Inserts an item into the column. Not implemented as this is controlled by the
- * PeaksWorkspace
- * @param index :: The new index position (unused)
- * @throw Exception::NotImplementedError
- */
-void LeanElasticPeakColumn::insert(size_t index) {
-  UNUSED_ARG(index);
-  throw Exception::NotImplementedError(
-      "LeanElasticPeakColumn::insert - Peaks must be "
-      "inserted through the PeaksWorkspace "
-      "interface.");
-}
-
-/**
- * Removes an item from the column. Not implemented as this is controlled by the
- * PeaksWorkspace
- * @param index :: The index position removed(unused)
- * @throw Exception::NotImplementedError
- */
-void LeanElasticPeakColumn::remove(size_t index) {
-  UNUSED_ARG(index);
-  throw Exception::NotImplementedError(
-      "LeanElasticPeakColumn::remove - Peaks must be "
-      "remove through the PeaksWorkspace "
-      "interface.");
-}
-
-/**
- * Pointer to a data element in the PeaksWorkspace (non-const version)
- * @param index :: A row index pointing to the PeaksWorkspace
- * @returns A pointer to the data element at that index from this column
- */
-void *LeanElasticPeakColumn::void_pointer(size_t index) {
-  const auto *constThis = const_cast<const LeanElasticPeakColumn *>(this);
-  return const_cast<void *>(constThis->void_pointer(index));
-}
-
-/**
- * Pointer to a data element in the PeaksWorkspace (const version)
- * @param index :: A row index pointing to the PeaksWorkspace
- * @returns A pointer to the data element at that index from this column
- */
-const void *LeanElasticPeakColumn::void_pointer(size_t index) const {
-  const LeanElasticPeak &peak = m_peaks[index];
-
-  // The cell() api requires that the value exist somewhere in memory, however,
-  // some of the values from a Peak are calculated on the fly so a reference
-  // cannot be returned. Instead we cache a value for the last NCELL_ITEM_CACHED
-  // accesses and return a reference to this
-
-  m_oldRows.push_front(CacheValueType());
-  if (m_oldRows.size() > NCELL_ITEM_CACHED) {
-    m_oldRows.pop_back();
-  }
-  auto &value = m_oldRows.front(); // A reference to the actual stored variant
-
-  if (type() == "double") {
-    value = peak.getValueByColName(m_name); // Assign the value to the store
-    return boost::get<double>(
-        &value); // Given a pointer it will return a pointer
-  } else if (m_name == "RunNumber") {
-    value = peak.getRunNumber();
-    return boost::get<int>(&value);
-  } else if (m_name == "PeakNumber") {
-    value = peak.getPeakNumber();
-    return boost::get<int>(&value);
-  } else if (m_name == "DetID") {
-    value = peak.getDetectorID();
-    return boost::get<int>(&value);
-  } else if (m_name == "BankName") {
-    value = peak.getBankName();
-    return boost::get<std::string>(&value);
-  } else if (m_name == "QLab") {
-    value = peak.getQLabFrame();
-    return boost::get<Kernel::V3D>(&value);
-  } else if (m_name == "QSample") {
-    value = peak.getQSampleFrame();
-    return boost::get<Kernel::V3D>(&value);
-  } else {
-    throw std::runtime_error(
-        "void_pointer() - Unknown peak column name or type: " + m_name);
-  }
-}
-
-LeanElasticPeakColumn *LeanElasticPeakColumn::clone() const {
-  auto temp = new LeanElasticPeakColumn(this->m_peaks, this->m_name);
-  return temp;
-}
-
-double LeanElasticPeakColumn::toDouble(size_t /*index*/) const {
-  throw std::runtime_error("LeanElasticPeakColumn::toDouble() not implemented, "
-                           "LeanElasticPeakColumn "
-                           "is has no general write access");
-}
-
-void LeanElasticPeakColumn::fromDouble(size_t /*index*/, double /*value*/) {
-  throw std::runtime_error(
-      "fromDouble() not implemented, LeanElasticPeakColumn is has no "
-      "general write access");
-}
-
-void LeanElasticPeakColumn::setPeakHKLOrRunNumber(const size_t index,
-                                                  const double val) {
-  LeanElasticPeak &peak = m_peaks[index];
-  if (m_name == "h")
-    peak.setH(val);
-  else if (m_name == "k")
-    peak.setK(val);
-  else if (m_name == "l")
-    peak.setL(val);
-  else if (m_name == "RunNumber")
-    peak.setRunNumber(static_cast<int>(val));
-  else
-    throw std::runtime_error("Unexpected column " + m_name + " being set.");
-}
-
-} // namespace DataObjects
-} // namespace Mantid
diff --git a/Framework/DataObjects/src/LeanElasticPeaksWorkspace.cpp b/Framework/DataObjects/src/LeanElasticPeaksWorkspace.cpp
index ab003c988b13ce4099e52fd8ec286cdbfeb2e70a..ce6b293de873bc083f7a3a5b52e0c673ab87b8c8 100644
--- a/Framework/DataObjects/src/LeanElasticPeaksWorkspace.cpp
+++ b/Framework/DataObjects/src/LeanElasticPeaksWorkspace.cpp
@@ -426,7 +426,8 @@ void LeanElasticPeaksWorkspace::initColumns() {
 void LeanElasticPeaksWorkspace::addPeakColumn(const std::string &name) {
   // Create the PeakColumn.
   columns.emplace_back(
-      std::make_shared<DataObjects::LeanElasticPeakColumn>(this->peaks, name));
+      std::make_shared<DataObjects::PeakColumn<LeanElasticPeak>>(this->peaks,
+                                                                 name));
   // Cache the names
   columnNames.emplace_back(name);
 }
diff --git a/Framework/DataObjects/src/PeakColumn.cpp b/Framework/DataObjects/src/PeakColumn.cpp
index a46a79b4ad99ee6927c98cf09103da8728836733..9b55ed13ed06991ce6eeb34c267002adcd56ca5d 100644
--- a/Framework/DataObjects/src/PeakColumn.cpp
+++ b/Framework/DataObjects/src/PeakColumn.cpp
@@ -84,7 +84,8 @@ const std::string typeFromName(const std::string &name) {
  * @param peaks :: vector of peaks
  * @param name :: name for the column
  */
-PeakColumn::PeakColumn(std::vector<Peak> &peaks, const std::string &name)
+template <class T>
+PeakColumn<T>::PeakColumn(std::vector<T> &peaks, const std::string &name)
     : m_peaks(peaks), m_oldRows() {
   this->m_name = name;
   this->m_type = typeFromName(name); // Throws if the name is unknown
@@ -100,7 +101,7 @@ PeakColumn::PeakColumn(std::vector<Peak> &peaks, const std::string &name)
 }
 
 /// Returns typeid for the data in the column
-const std::type_info &PeakColumn::get_type_info() const {
+template <class T> const std::type_info &PeakColumn<T>::get_type_info() const {
   // This is horrible copy-and-paste with the method below. The whole thing
   // around columns could be much better implemented using templates & traits to
   // avoid this
@@ -121,7 +122,8 @@ const std::type_info &PeakColumn::get_type_info() const {
 }
 
 /// Returns typeid for the pointer type to the data element in the column
-const std::type_info &PeakColumn::get_pointer_type_info() const {
+template <class T>
+const std::type_info &PeakColumn<T>::get_pointer_type_info() const {
   if (type() == "double") {
     return typeid(double *);
   } else if (type() == "int") {
@@ -142,8 +144,9 @@ const std::type_info &PeakColumn::get_pointer_type_info() const {
  * @param s :: stream to output
  * @param index :: row index
  */
-void PeakColumn::print(size_t index, std::ostream &s) const {
-  Peak &peak = m_peaks[index];
+template <class T>
+void PeakColumn<T>::print(size_t index, std::ostream &s) const {
+  T &peak = m_peaks[index];
   s.imbue(std::locale("C"));
   std::ios::fmtflags fflags(s.flags());
   if (m_name == "RunNumber")
@@ -175,7 +178,8 @@ void PeakColumn::print(size_t index, std::ostream &s) const {
  * @param text :: string to read
  * @param index :: index of the peak to modify
  */
-void PeakColumn::read(size_t index, const std::string &text) {
+template <class T>
+void PeakColumn<T>::read(size_t index, const std::string &text) {
   // Don't modify read-only ones
   if (this->getReadOnly() || index >= m_peaks.size())
     return;
@@ -196,7 +200,8 @@ void PeakColumn::read(size_t index, const std::string &text) {
  * @param index :: index of the peak to modify
  * @param in :: input stream
  */
-void PeakColumn::read(const size_t index, std::istringstream &in) {
+template <class T>
+void PeakColumn<T>::read(const size_t index, std::istringstream &in) {
   if (this->getReadOnly() || index >= m_peaks.size())
     return;
 
@@ -214,19 +219,19 @@ void PeakColumn::read(const size_t index, std::istringstream &in) {
 
 //-------------------------------------------------------------------------------------
 /** @return true if the column is read-only */
-bool PeakColumn::getReadOnly() const {
+template <class T> bool PeakColumn<T>::getReadOnly() const {
   return !((m_name == "h") || (m_name == "k") || (m_name == "l") ||
            (m_name == "RunNumber"));
 }
 
 //-------------------------------------------------------------------------------------
 /// Specialized type check
-bool PeakColumn::isBool() const { return false; }
+template <class T> bool PeakColumn<T>::isBool() const { return false; }
 
-bool PeakColumn::isNumber() const { return false; }
+template <class T> bool PeakColumn<T>::isNumber() const { return false; }
 
 /// @returns overall memory size taken by the column.
-long int PeakColumn::sizeOfData() const {
+template <class T> long int PeakColumn<T>::sizeOfData() const {
   return sizeof(double) * static_cast<long int>(m_peaks.size());
 }
 
@@ -236,7 +241,7 @@ long int PeakColumn::sizeOfData() const {
  * @param count :: Count of new column size (unused)
  * @throw Exception::NotImplementedError
  */
-void PeakColumn::resize(size_t count) {
+template <class T> void PeakColumn<T>::resize(size_t count) {
   UNUSED_ARG(count);
   throw Exception::NotImplementedError("PeakColumn::resize - Peaks must be "
                                        "added through the PeaksWorkspace "
@@ -249,7 +254,7 @@ void PeakColumn::resize(size_t count) {
  * @param index :: The new index position (unused)
  * @throw Exception::NotImplementedError
  */
-void PeakColumn::insert(size_t index) {
+template <class T> void PeakColumn<T>::insert(size_t index) {
   UNUSED_ARG(index);
   throw Exception::NotImplementedError("PeakColumn::insert - Peaks must be "
                                        "inserted through the PeaksWorkspace "
@@ -262,7 +267,7 @@ void PeakColumn::insert(size_t index) {
  * @param index :: The index position removed(unused)
  * @throw Exception::NotImplementedError
  */
-void PeakColumn::remove(size_t index) {
+template <class T> void PeakColumn<T>::remove(size_t index) {
   UNUSED_ARG(index);
   throw Exception::NotImplementedError("PeakColumn::remove - Peaks must be "
                                        "remove through the PeaksWorkspace "
@@ -274,7 +279,7 @@ void PeakColumn::remove(size_t index) {
  * @param index :: A row index pointing to the PeaksWorkspace
  * @returns A pointer to the data element at that index from this column
  */
-void *PeakColumn::void_pointer(size_t index) {
+template <class T> void *PeakColumn<T>::void_pointer(size_t index) {
   const auto *constThis = const_cast<const PeakColumn *>(this);
   return const_cast<void *>(constThis->void_pointer(index));
 }
@@ -284,8 +289,8 @@ void *PeakColumn::void_pointer(size_t index) {
  * @param index :: A row index pointing to the PeaksWorkspace
  * @returns A pointer to the data element at that index from this column
  */
-const void *PeakColumn::void_pointer(size_t index) const {
-  const Peak &peak = m_peaks[index];
+template <class T> const void *PeakColumn<T>::void_pointer(size_t index) const {
+  const T &peak = m_peaks[index];
 
   // The cell() api requires that the value exist somewhere in memory, however,
   // some of the values from a Peak are calculated on the fly so a reference
@@ -326,23 +331,26 @@ const void *PeakColumn::void_pointer(size_t index) const {
   }
 }
 
-PeakColumn *PeakColumn::clone() const {
-  auto temp = new PeakColumn(this->m_peaks, this->m_name);
+template <class T> PeakColumn<T> *PeakColumn<T>::clone() const {
+  auto temp = new PeakColumn<T>(this->m_peaks, this->m_name);
   return temp;
 }
 
-double PeakColumn::toDouble(size_t /*index*/) const {
+template <class T> double PeakColumn<T>::toDouble(size_t /*index*/) const {
   throw std::runtime_error("PeakColumn::toDouble() not implemented, PeakColumn "
                            "is has no general write access");
 }
 
-void PeakColumn::fromDouble(size_t /*index*/, double /*value*/) {
+template <class T>
+void PeakColumn<T>::fromDouble(size_t /*index*/, double /*value*/) {
   throw std::runtime_error("fromDouble() not implemented, PeakColumn is has no "
                            "general write access");
 }
 
-void PeakColumn::setPeakHKLOrRunNumber(const size_t index, const double val) {
-  Peak &peak = m_peaks[index];
+template <class T>
+void PeakColumn<T>::setPeakHKLOrRunNumber(const size_t index,
+                                          const double val) {
+  T &peak = m_peaks[index];
   if (m_name == "h")
     peak.setH(val);
   else if (m_name == "k")
@@ -355,5 +363,8 @@ void PeakColumn::setPeakHKLOrRunNumber(const size_t index, const double val) {
     throw std::runtime_error("Unexpected column " + m_name + " being set.");
 }
 
+template class PeakColumn<Peak>;
+template class PeakColumn<LeanElasticPeak>;
+
 } // namespace DataObjects
 } // namespace Mantid
diff --git a/Framework/DataObjects/src/PeaksWorkspace.cpp b/Framework/DataObjects/src/PeaksWorkspace.cpp
index 52f7be186a9bd892b9d076122e6fb2e120da1b33..74094529b3713d1bd72f766b133555adc8a3fcd7 100644
--- a/Framework/DataObjects/src/PeaksWorkspace.cpp
+++ b/Framework/DataObjects/src/PeaksWorkspace.cpp
@@ -659,7 +659,7 @@ void PeaksWorkspace::initColumns() {
 void PeaksWorkspace::addPeakColumn(const std::string &name) {
   // Create the PeakColumn.
   columns.emplace_back(
-      std::make_shared<DataObjects::PeakColumn>(this->peaks, name));
+      std::make_shared<DataObjects::PeakColumn<Peak>>(this->peaks, name));
   // Cache the names
   columnNames.emplace_back(name);
 }
diff --git a/Framework/DataObjects/test/LeanElasticPeakColumnTest.h b/Framework/DataObjects/test/LeanElasticPeakColumnTest.h
deleted file mode 100644
index 1e1d70ed591f3e837cc93823d302a9594ab7aff5..0000000000000000000000000000000000000000
--- a/Framework/DataObjects/test/LeanElasticPeakColumnTest.h
+++ /dev/null
@@ -1,182 +0,0 @@
-// Mantid Repository : https://github.com/mantidproject/mantid
-//
-// Copyright &copy; 2018 ISIS Rutherford Appleton Laboratory UKRI,
-//   NScD Oak Ridge National Laboratory, European Spallation Source,
-//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
-// SPDX - License - Identifier: GPL - 3.0 +
-#pragma once
-
-#include "MantidDataObjects/LeanElasticPeak.h"
-#include "MantidDataObjects/LeanElasticPeakColumn.h"
-#include "MantidKernel/Exception.h"
-#include <cxxtest/TestSuite.h>
-
-#include "MantidTestHelpers/ComponentCreationHelper.h"
-
-#include <locale>
-#include <memory>
-
-using namespace Mantid::DataObjects;
-
-// Allow testing of protected methods
-class LeanElasticPeakColumnTestHelper : public LeanElasticPeakColumn {
-public:
-  LeanElasticPeakColumnTestHelper(std::vector<LeanElasticPeak> &peaks,
-                                  const std::string &name)
-      : LeanElasticPeakColumn(peaks, name) {}
-
-  using LeanElasticPeakColumn::insert;
-  using LeanElasticPeakColumn::remove;
-  using LeanElasticPeakColumn::resize;
-};
-
-class LeanElasticPeakColumnTest : public CxxTest::TestSuite {
-public:
-  // This pair of boilerplate methods prevent the suite being created statically
-  // This means the constructor isn't called when running other tests
-  static LeanElasticPeakColumnTest *createSuite() {
-    return new LeanElasticPeakColumnTest();
-  }
-  static void destroySuite(LeanElasticPeakColumnTest *suite) { delete suite; }
-
-  LeanElasticPeakColumnTest() : m_peaks(2) {
-    m_peaks[0] = LeanElasticPeak(Mantid::Kernel::V3D(1, 1, 1), 4.0);
-    m_peaks[1] = LeanElasticPeak(Mantid::Kernel::V3D(1, 1, 0), 4.1);
-  }
-
-  void test_constructor_create_valid_object_when_given_valid_name() {
-    LeanElasticPeakColumn pc(m_peaks, "h");
-    TS_ASSERT_EQUALS(pc.name(), "h");
-    TS_ASSERT_EQUALS(pc.size(), 2);
-  }
-
-  void test_constructor_throws_given_unknown_name() {
-    TS_ASSERT_THROWS(LeanElasticPeakColumn(m_peaks, "NotLeanElasticPeakColumn"),
-                     const std::runtime_error &);
-  }
-
-  void test_clone() {
-    LeanElasticPeakColumn pc(m_peaks, "h");
-    LeanElasticPeakColumn *cloned = pc.clone();
-
-    TS_ASSERT_EQUALS(pc.name(), cloned->name());
-    TS_ASSERT_EQUALS(2, cloned->size());
-
-    delete cloned;
-  }
-
-  void test_type_info_is_expected_type_based_on_string_type() {
-    LeanElasticPeakColumnTestHelper pcInt(m_peaks, "DetID");
-    TS_ASSERT(pcInt.get_type_info() == typeid(int));
-    TS_ASSERT(pcInt.get_pointer_type_info() == typeid(int *));
-
-    LeanElasticPeakColumnTestHelper pcDouble(m_peaks, "h");
-    TS_ASSERT(pcDouble.get_type_info() == typeid(double));
-    TS_ASSERT(pcDouble.get_pointer_type_info() == typeid(double *));
-
-    LeanElasticPeakColumnTestHelper pcStr(m_peaks, "BankName");
-    TS_ASSERT(pcStr.get_type_info() == typeid(std::string));
-    TS_ASSERT(pcStr.get_pointer_type_info() == typeid(std::string *));
-
-    LeanElasticPeakColumnTestHelper pcV3D(m_peaks, "QLab");
-    TS_ASSERT(pcV3D.get_type_info() == typeid(Mantid::Kernel::V3D));
-    TS_ASSERT(pcV3D.get_pointer_type_info() == typeid(Mantid::Kernel::V3D *));
-  }
-
-  void test_LeanElasticPeakColumn_Cannot_Be_Resized() {
-    LeanElasticPeakColumnTestHelper pc(m_peaks, "DetID");
-    TS_ASSERT_THROWS(pc.resize(10),
-                     const Mantid::Kernel::Exception::NotImplementedError &);
-  }
-
-  void test_Row_Cannot_Be_Inserted_Into_LeanElasticPeakColumn() {
-    LeanElasticPeakColumnTestHelper pc(m_peaks, "DetID");
-    TS_ASSERT_THROWS(pc.insert(0),
-                     const Mantid::Kernel::Exception::NotImplementedError &);
-  }
-
-  void test_Row_Cannot_Be_Removed_From_LeanElasticPeakColumn() {
-    LeanElasticPeakColumnTestHelper pc(m_peaks, "DetID");
-    TS_ASSERT_THROWS(pc.remove(0),
-                     const Mantid::Kernel::Exception::NotImplementedError &);
-  }
-
-  void test_cell_returns_correct_value_from_LeanElasticPeakColumn() {
-    LeanElasticPeakColumn pc1(m_peaks, "DetID");
-    int detId = pc1.cell<int>(0);
-    TS_ASSERT_EQUALS(-1, detId);
-    detId = pc1.cell<int>(1);
-    TS_ASSERT_EQUALS(-1, detId);
-
-    LeanElasticPeakColumn pc2(m_peaks, "QLab");
-    const Mantid::Kernel::V3D &qlab0 = pc2.cell<Mantid::Kernel::V3D>(0);
-    TS_ASSERT_EQUALS(qlab0, m_peaks[0].getQLabFrame());
-    const Mantid::Kernel::V3D &qlab1 = pc2.cell<Mantid::Kernel::V3D>(1);
-    TS_ASSERT_EQUALS(qlab1, m_peaks[1].getQLabFrame());
-  }
-
-  void test_get_read_only_returns_correct_value() {
-    LeanElasticPeakColumn pc1(m_peaks, "h");
-    auto readOnly = pc1.getReadOnly();
-    TS_ASSERT(!readOnly);
-
-    LeanElasticPeakColumn pc2(m_peaks, "DetID");
-    readOnly = pc2.getReadOnly();
-    TS_ASSERT(readOnly);
-  }
-
-  void test_read_locale_awerness() {
-    const std::vector<std::string> columnNames{"h", "k", "l", "RunNumber"};
-    const std::vector<double> columnValues{-2.0, 5.0, 12.0, 143290.0};
-    TestingNumpunctFacet numpunct;
-    const std::locale testLocale(std::locale::classic(), &numpunct);
-    for (size_t i = 0; i < columnNames.size(); ++i) {
-      LeanElasticPeakColumn pc(m_peaks, columnNames[i]);
-      // Use a fake punctuation facet for numeric formatting.
-      std::ostringstream out;
-      out.imbue(testLocale);
-      // Force some decimals to the numbers.
-      out << std::fixed;
-      out << std::setprecision(2);
-      out << columnValues[i];
-      std::istringstream in(out.str());
-      in.imbue(testLocale);
-      pc.read(0, in);
-      switch (i) {
-      case 0:
-        TS_ASSERT_EQUALS(m_peaks[0].getH(), columnValues[i])
-        break;
-      case 1:
-        TS_ASSERT_EQUALS(m_peaks[0].getK(), columnValues[i])
-        break;
-      case 2:
-        TS_ASSERT_EQUALS(m_peaks[0].getL(), columnValues[i])
-        break;
-      case 3:
-        TS_ASSERT_EQUALS(m_peaks[0].getRunNumber(), columnValues[i])
-        break;
-      }
-    }
-  }
-
-  void test_cannot_be_converted_to_double() {
-    LeanElasticPeakColumn col(m_peaks, "DetID");
-    TS_ASSERT(!col.isNumber());
-  }
-
-private:
-  /// A locale facet mocking non-english numerical punctuation.
-  class TestingNumpunctFacet : public std::numpunct<char> {
-  public:
-    /// Informs locales not to delete this facet.
-    TestingNumpunctFacet() : std::numpunct<char>(1) {}
-
-  private:
-    char_type do_decimal_point() const override { return '%'; }
-    char_type do_thousands_sep() const override { return '@'; }
-    string_type do_grouping() const override { return "\03"; }
-  };
-
-  Mantid::Geometry::Instrument_sptr m_inst;
-  std::vector<LeanElasticPeak> m_peaks;
-};
diff --git a/Framework/DataObjects/test/PeakColumnTest.h b/Framework/DataObjects/test/PeakColumnTest.h
index 3a240bdbe1e91fc68c68881cb53ca471869d000b..6540dcac693b4d3a0eafed44dc2785702ef5f09f 100644
--- a/Framework/DataObjects/test/PeakColumnTest.h
+++ b/Framework/DataObjects/test/PeakColumnTest.h
@@ -19,10 +19,10 @@
 using namespace Mantid::DataObjects;
 
 // Allow testing of protected methods
-class PeakColumnTestHelper : public PeakColumn {
+class PeakColumnTestHelper : public PeakColumn<Peak> {
 public:
   PeakColumnTestHelper(std::vector<Peak> &peaks, const std::string &name)
-      : PeakColumn(peaks, name) {}
+      : PeakColumn<Peak>(peaks, name) {}
 
   using PeakColumn::insert;
   using PeakColumn::remove;
@@ -43,19 +43,19 @@ public:
   }
 
   void test_constructor_create_valid_object_when_given_valid_name() {
-    PeakColumn pc(m_peaks, "h");
+    PeakColumn<Peak> pc(m_peaks, "h");
     TS_ASSERT_EQUALS(pc.name(), "h");
     TS_ASSERT_EQUALS(pc.size(), 2);
   }
 
   void test_constructor_throws_given_unknown_name() {
-    TS_ASSERT_THROWS(PeakColumn(m_peaks, "NotPeakColumn"),
+    TS_ASSERT_THROWS(PeakColumn<Peak>(m_peaks, "NotPeakColumn"),
                      const std::runtime_error &);
   }
 
   void test_clone() {
-    PeakColumn pc(m_peaks, "h");
-    PeakColumn *cloned = pc.clone();
+    PeakColumn<Peak> pc(m_peaks, "h");
+    PeakColumn<Peak> *cloned = pc.clone();
 
     TS_ASSERT_EQUALS(pc.name(), cloned->name());
     TS_ASSERT_EQUALS(2, cloned->size());
@@ -100,13 +100,13 @@ public:
   }
 
   void test_cell_returns_correct_value_from_PeakColumn() {
-    PeakColumn pc1(m_peaks, "DetID");
+    PeakColumn<Peak> pc1(m_peaks, "DetID");
     int detId = pc1.cell<int>(0);
     TS_ASSERT_EQUALS(1, detId);
     detId = pc1.cell<int>(1);
     TS_ASSERT_EQUALS(2, detId);
 
-    PeakColumn pc2(m_peaks, "QLab");
+    PeakColumn<Peak> pc2(m_peaks, "QLab");
     const Mantid::Kernel::V3D &qlab0 = pc2.cell<Mantid::Kernel::V3D>(0);
     TS_ASSERT_EQUALS(qlab0, m_peaks[0].getQLabFrame());
     const Mantid::Kernel::V3D &qlab1 = pc2.cell<Mantid::Kernel::V3D>(1);
@@ -114,11 +114,11 @@ public:
   }
 
   void test_get_read_only_returns_correct_value() {
-    PeakColumn pc1(m_peaks, "h");
+    PeakColumn<Peak> pc1(m_peaks, "h");
     auto readOnly = pc1.getReadOnly();
     TS_ASSERT(!readOnly);
 
-    PeakColumn pc2(m_peaks, "DetID");
+    PeakColumn<Peak> pc2(m_peaks, "DetID");
     readOnly = pc2.getReadOnly();
     TS_ASSERT(readOnly);
   }
@@ -129,7 +129,7 @@ public:
     TestingNumpunctFacet numpunct;
     const std::locale testLocale(std::locale::classic(), &numpunct);
     for (size_t i = 0; i < columnNames.size(); ++i) {
-      PeakColumn pc(m_peaks, columnNames[i]);
+      PeakColumn<Peak> pc(m_peaks, columnNames[i]);
       // Use a fake punctuation facet for numeric formatting.
       std::ostringstream out;
       out.imbue(testLocale);
@@ -158,7 +158,7 @@ public:
   }
 
   void test_cannot_be_converted_to_double() {
-    PeakColumn col(m_peaks, "DetID");
+    PeakColumn<Peak> col(m_peaks, "DetID");
     TS_ASSERT(!col.isNumber());
   }