diff --git a/Framework/API/inc/MantidAPI/ExperimentInfo.h b/Framework/API/inc/MantidAPI/ExperimentInfo.h
index 8f92d8a11f10307c9acce28bfae39ff70e04aa68..6f128842aa3db78a81c5de5c8172ba444cb10cba 100644
--- a/Framework/API/inc/MantidAPI/ExperimentInfo.h
+++ b/Framework/API/inc/MantidAPI/ExperimentInfo.h
@@ -166,8 +166,6 @@ public:
   const SpectrumInfo &spectrumInfo() const;
   SpectrumInfo &mutableSpectrumInfo();
 
-  virtual size_t numberOfDetectorGroups() const;
-  virtual const std::set<detid_t> detectorIDsInGroup(const size_t index) const;
   virtual size_t groupOfDetectorID(const detid_t detID) const;
 
 protected:
diff --git a/Framework/API/inc/MantidAPI/MatrixWorkspace.h b/Framework/API/inc/MantidAPI/MatrixWorkspace.h
index 72cfc662f68f8165f8da761cbad826a9e9080929..c1a88671d529951133042ee75adee03a514c44b4 100644
--- a/Framework/API/inc/MantidAPI/MatrixWorkspace.h
+++ b/Framework/API/inc/MantidAPI/MatrixWorkspace.h
@@ -538,9 +538,6 @@ public:
   //=====================================================================================
 
   void cacheDetectorGroupings(const det2group_map &mapping) override;
-
-  size_t numberOfDetectorGroups() const override;
-  const std::set<detid_t> detectorIDsInGroup(const size_t index) const override;
   size_t groupOfDetectorID(const detid_t detID) const override;
 
 protected:
diff --git a/Framework/API/inc/MantidAPI/SpectrumInfo.h b/Framework/API/inc/MantidAPI/SpectrumInfo.h
index 33d2c94f96feb59c9b6e975115c9e1160954edce..3f3c77eb51019cd9da346d173b47c0ae9d759107 100644
--- a/Framework/API/inc/MantidAPI/SpectrumInfo.h
+++ b/Framework/API/inc/MantidAPI/SpectrumInfo.h
@@ -65,6 +65,8 @@ public:
   SpectrumInfo(ExperimentInfo &experimentInfo);
   ~SpectrumInfo();
 
+  size_t size() const;
+
   bool isMonitor(const size_t index) const;
   bool isMasked(const size_t index) const;
   double l2(const size_t index) const;
diff --git a/Framework/API/src/ExperimentInfo.cpp b/Framework/API/src/ExperimentInfo.cpp
index b81695bdc4a3eb187e583358b0559efa0033ec12..27a51f6c7169eb7b9ce1cfd83d86a62b72df08df 100644
--- a/Framework/API/src/ExperimentInfo.cpp
+++ b/Framework/API/src/ExperimentInfo.cpp
@@ -1109,48 +1109,6 @@ SpectrumInfo &ExperimentInfo::mutableSpectrumInfo() {
   return *m_spectrumInfoWrapper;
 }
 
-/** Returns the number of detector groups.
- *
- * This is a virtual method. The default implementation returns grouping
- * information cached in the ExperimentInfo. This is used in MDAlgorithms. The
- * more common case is handled by the overload of this method in
- * MatrixWorkspace. The purpose of this method is to be able to construct
- * SpectrumInfo based on an ExperimentInfo object, including grouping
- * information. Grouping information can be cached in ExperimentInfo, or can be
- * obtained from child classes (MatrixWorkspace). */
-size_t ExperimentInfo::numberOfDetectorGroups() const {
-  populateIfNotLoaded();
-  std::call_once(m_defaultDetectorGroupingCached,
-                 &ExperimentInfo::cacheDefaultDetectorGrouping, this);
-
-  return m_spectrumInfo->size();
-}
-
-/** Returns a set of detector IDs for a group.
- *
- * This is a virtual method. The default implementation returns grouping
- * information cached in the ExperimentInfo. This is used in MDAlgorithms. The
- * more common case is handled by the overload of this method in
- * MatrixWorkspace. The purpose of this method is to be able to construct
- * SpectrumInfo based on an ExperimentInfo object, including grouping
- * information. Grouping information can be cached in ExperimentInfo, or can be
- * obtained from child classes (MatrixWorkspace). */
-const std::set<detid_t>
-ExperimentInfo::detectorIDsInGroup(const size_t index) const {
-  populateIfNotLoaded();
-  std::call_once(m_defaultDetectorGroupingCached,
-                 &ExperimentInfo::cacheDefaultDetectorGrouping, this);
-
-  const auto &detectorIDs = detectorInfo().detectorIDs();
-  std::set<detid_t> detIDs;
-  for (const auto detIndex : m_spectrumInfo->spectrumDefinition(index)) {
-    // first is the detector index. Time index (second) ignored for now.
-    detIDs.emplace(detectorIDs[detIndex.first]);
-  }
-
-  return detIDs;
-}
-
 /** Sets up a default detector grouping.
  *
  * The purpose of this method is to work around potential issues of MDWorkspaces
diff --git a/Framework/API/src/MatrixWorkspace.cpp b/Framework/API/src/MatrixWorkspace.cpp
index 9dfe763aa674f674d113329d58aba0a2be8b8d15..99a1e2bcebac5f190e3f607509f3793e33cb651e 100644
--- a/Framework/API/src/MatrixWorkspace.cpp
+++ b/Framework/API/src/MatrixWorkspace.cpp
@@ -1960,19 +1960,6 @@ void MatrixWorkspace::cacheDetectorGroupings(const det2group_map &) {
                            "spectra");
 }
 
-/// Returns the number of detector groups. This is equal to the number of
-/// spectra.
-size_t MatrixWorkspace::numberOfDetectorGroups() const {
-  return getNumberHistograms();
-}
-
-/// Returns a set of detector IDs for a group. This is equal to the detector IDs
-/// of the spectrum at given index.
-const std::set<detid_t>
-MatrixWorkspace::detectorIDsInGroup(const size_t index) const {
-  return getSpectrum(index).getDetectorIDs();
-}
-
 /// Throws an exception. This method is only for MDWorkspaces.
 size_t MatrixWorkspace::groupOfDetectorID(const detid_t) const {
   throw std::runtime_error("ExperimentInfo::groupOfDetectorID can not be used "
diff --git a/Framework/API/src/SpectrumInfo.cpp b/Framework/API/src/SpectrumInfo.cpp
index 7ccf43acb804d03c0ad25305c83d2e8664a3b9f8..e6bfb536ed003929a5bf57fbb1a120273720b1d5 100644
--- a/Framework/API/src/SpectrumInfo.cpp
+++ b/Framework/API/src/SpectrumInfo.cpp
@@ -29,6 +29,11 @@ SpectrumInfo::SpectrumInfo(ExperimentInfo &experimentInfo)
 // Defined as default in source for forward declaration with std::unique_ptr.
 SpectrumInfo::~SpectrumInfo() = default;
 
+/// Returns the size of the SpectrumInfo, i.e., the number of spectra.
+size_t SpectrumInfo::size() const {
+  return m_experimentInfo.internalSpectrumInfo().size();
+}
+
 /// Returns true if the detector(s) associated with the spectrum are monitors.
 bool SpectrumInfo::isMonitor(const size_t index) const {
   for (const auto detIndex : getDetectorIndices(index))
diff --git a/Framework/API/test/CMakeLists.txt b/Framework/API/test/CMakeLists.txt
index 9f1fe2a12caa434d73abb6902dcfaefd41c08108..6274317d2f64498f58145a2e35fb79869dfe42d0 100644
--- a/Framework/API/test/CMakeLists.txt
+++ b/Framework/API/test/CMakeLists.txt
@@ -17,6 +17,7 @@ if ( CXXTEST_FOUND )
             Geometry
             HistogramData
             Indexing
+            Beamline
             Kernel
             Nexus
             ${JSONCPP_LIBRARIES}
diff --git a/Framework/API/test/ExperimentInfoTest.h b/Framework/API/test/ExperimentInfoTest.h
index cfac0aeee33a970dc8f850f88cf60cf0c21c6dbc..ebb59b3e4bf6e99c293d19230f3381a1ce4b2f45 100644
--- a/Framework/API/test/ExperimentInfoTest.h
+++ b/Framework/API/test/ExperimentInfoTest.h
@@ -9,6 +9,8 @@
 #include "MantidAPI/Sample.h"
 #include "MantidGeometry/Crystal/OrientedLattice.h"
 #include "MantidGeometry/Instrument/DetectorGroup.h"
+#include "MantidBeamline/SpectrumDefinition.h"
+#include "MantidBeamline/SpectrumInfo.h"
 #include "MantidKernel/ConfigService.h"
 #include "MantidKernel/DateAndTime.h"
 #include "MantidKernel/SingletonHolder.h"
@@ -412,22 +414,26 @@ public:
     TS_ASSERT_EQUALS(exptInfo->getEFixed(test_id), test_ef);
   }
 
-  void test_detectorIDsInGroup() {
+  void test_cacheDetectorGroupings_creates_correct_SpectrumInfo() {
     using namespace Mantid;
     ExperimentInfo_sptr exptInfo(new ExperimentInfo);
     addInstrumentWithParameter(*exptInfo, "a", "b");
 
-    std::set<detid_t> dets;
-    TS_ASSERT_THROWS_NOTHING(dets = exptInfo->detectorIDsInGroup(0));
-    TS_ASSERT_EQUALS(dets, std::set<detid_t>{1});
+    const auto &spectrumInfo = exptInfo->internalSpectrumInfo();
+    const auto &spectrumDefinition = spectrumInfo.spectrumDefinition(0);
+    TS_ASSERT_EQUALS(spectrumDefinition.size(), 1);
+    TS_ASSERT_EQUALS(spectrumDefinition[0].first, 0); // det index 0 (ID 1)
 
     // Set a mapping
     std::set<detid_t> group{1, 2};
     Mantid::det2group_map mapping{{1, group}};
     exptInfo->cacheDetectorGroupings(mapping);
 
-    TS_ASSERT_THROWS_NOTHING(dets = exptInfo->detectorIDsInGroup(0));
-    TS_ASSERT_EQUALS(dets, group);
+    const auto &spectrumInfo2 = exptInfo->internalSpectrumInfo();
+    const auto &spectrumDefinition2 = spectrumInfo2.spectrumDefinition(0);
+    TS_ASSERT_EQUALS(spectrumDefinition2.size(), 2);
+    TS_ASSERT_EQUALS(spectrumDefinition2[0].first, 0); // det index 0 (ID 1)
+    TS_ASSERT_EQUALS(spectrumDefinition2[1].first, 1); // det index 1 (ID 2)
   }
 
   void test_Setting_Group_Lookup_To_Empty_Map_Does_Not_Throw() {
diff --git a/Framework/API/test/FileBackedExperimentInfoTest.h b/Framework/API/test/FileBackedExperimentInfoTest.h
index 8b7f964793f095922d8e306d204370524a1cc3c4..d8fb9ab4ecf960ef07be54eb13530a48dfe8ddeb 100644
--- a/Framework/API/test/FileBackedExperimentInfoTest.h
+++ b/Framework/API/test/FileBackedExperimentInfoTest.h
@@ -123,12 +123,6 @@ public:
     fileBacked->cacheDetectorGroupings(mapping);
   }
 
-  void test_detectorIDsInGroup() {
-    auto fileBacked = createTestObject();
-
-    TS_ASSERT_EQUALS(fileBacked->detectorIDsInGroup(0).size(), 1);
-  }
-
   void test_ModeratorModelMethods() {
     auto fileBacked = createTestObject();
     ModeratorModel *source = new FakeSource;
diff --git a/Framework/API/test/SpectrumInfoTest.h b/Framework/API/test/SpectrumInfoTest.h
index c5075ad0e90ce0e09ac21485e72c755ac7b94141..5a17619db73a464cf9088ee76e4129a38c1b776e 100644
--- a/Framework/API/test/SpectrumInfoTest.h
+++ b/Framework/API/test/SpectrumInfoTest.h
@@ -430,8 +430,8 @@ public:
 
   void test_ExperimentInfo_from_grouped() {
     const ExperimentInfo expInfo(m_grouped);
-    TS_ASSERT_EQUALS(expInfo.numberOfDetectorGroups(), 5);
-    const SpectrumInfo spectrumInfo(expInfo);
+    const auto &spectrumInfo = expInfo.spectrumInfo();
+    TS_ASSERT_EQUALS(spectrumInfo.size(), 5);
     // We construct from a grouped workspace (via ISpectrum), but grouping is
     // now stored in Beamline::SpectrumInfo as part of ExperimentInfo, so we
     // should also see the grouping here.
@@ -450,14 +450,14 @@ public:
     // we have no control over the order and thus cannot write asserts.
     det2group_map mapping{{1, {1, 2}}};
     expInfo.cacheDetectorGroupings(mapping);
-    const SpectrumInfo spectrumInfo(expInfo);
-    TS_ASSERT_EQUALS(expInfo.numberOfDetectorGroups(), 1);
+    const auto &spectrumInfo = expInfo.spectrumInfo();
+    TS_ASSERT_EQUALS(spectrumInfo.size(), 1);
     TS_ASSERT_EQUALS(spectrumInfo.isMasked(0), false);
 
     mapping = {{1, {1, 4}}};
     expInfo.cacheDetectorGroupings(mapping);
-    const SpectrumInfo spectrumInfo2(expInfo);
-    TS_ASSERT_EQUALS(expInfo.numberOfDetectorGroups(), 1);
+    const auto &spectrumInfo2 = expInfo.spectrumInfo();
+    TS_ASSERT_EQUALS(spectrumInfo.size(), 1);
     TS_ASSERT_EQUALS(spectrumInfo2.isMasked(0), true);
   }
 
diff --git a/Framework/MDAlgorithms/src/MDNormDirectSC.cpp b/Framework/MDAlgorithms/src/MDNormDirectSC.cpp
index d4d7a5a0733ce25e5be3fa6373bcded39537ae23..b2452e8332f6b37d822e3fe3afd122a69d9731b7 100644
--- a/Framework/MDAlgorithms/src/MDNormDirectSC.cpp
+++ b/Framework/MDAlgorithms/src/MDNormDirectSC.cpp
@@ -443,11 +443,10 @@ void MDNormDirectSC::calculateNormalization(
   }
   const double protonCharge = exptInfoZero.run().getProtonCharge();
 
-  const SpectrumInfo spectrumInfo(exptInfoZero);
+  const auto &spectrumInfo = exptInfoZero.spectrumInfo();
 
   // Mapping
-  const int64_t ndets =
-      static_cast<int64_t>(exptInfoZero.numberOfDetectorGroups());
+  const int64_t ndets = static_cast<int64_t>(spectrumInfo.size());
   bool haveSA = false;
   API::MatrixWorkspace_const_sptr solidAngleWS =
       getProperty("SolidAngleWorkspace");
diff --git a/Framework/MDAlgorithms/src/MDNormSCD.cpp b/Framework/MDAlgorithms/src/MDNormSCD.cpp
index 4d59359dfa0a8e0fa180ea11dd2dd3e05b67391d..4a26e440bbdec155a92c3dd9846d86ce25912f48 100644
--- a/Framework/MDAlgorithms/src/MDNormSCD.cpp
+++ b/Framework/MDAlgorithms/src/MDNormSCD.cpp
@@ -393,11 +393,10 @@ void MDNormSCD::calculateNormalization(
   }
   const double protonCharge = exptInfoZero.run().getProtonCharge();
 
-  const SpectrumInfo spectrumInfo(exptInfoZero);
+  const auto &spectrumInfo = exptInfoZero.spectrumInfo();
 
   // Mappings
-  const int64_t ndets =
-      static_cast<int64_t>(exptInfoZero.numberOfDetectorGroups());
+  const int64_t ndets = static_cast<int64_t>(spectrumInfo.size());
   const detid2index_map fluxDetToIdx =
       integrFlux->getDetectorIDToWorkspaceIndexMap();
   const detid2index_map solidAngDetToIdx =