diff --git a/docs/source/interfaces/Engineering Diffraction 2.rst b/docs/source/interfaces/Engineering Diffraction 2.rst
index c2c7bf1d40ea2ee7fe43f9d43d47c121220a92c9..d5bd85d3d559efba8e7a7f02df4e0417445e79a9 100644
--- a/docs/source/interfaces/Engineering Diffraction 2.rst	
+++ b/docs/source/interfaces/Engineering Diffraction 2.rst	
@@ -11,7 +11,9 @@ Interface Overview
 
 This custom interface will integrate several tasks related to engineering
 diffraction. In its current state it provides functionality for creating
-new calibration files. This interface is under active development.
+and loading calibration files, and focusing ENGINX run files.
+
+This interface is under active development.
 
 General Options
 ^^^^^^^^^^^^^^^
@@ -27,6 +29,11 @@ Instrument
 ?
     Show this documentation page.
 
+Settings
+    Provides a range of options that apply across the entire interface, currently
+    providing the option to change the default output directory and force the
+    recalculation of the vanadium correction files.
+
 Close
     Close the interface.
 
@@ -37,9 +44,10 @@ Red Stars
 Calibration
 -----------
 
-This tab currently provides a graphical interface to create new calibrations
-and visualise them. It also allows for the loading of GSAS parameter files created
-by the calibration process to load a previously created calibration into the interface.
+This tab currently provides a graphical interface to create new calibrations, using the
+:ref:`EnggCalibrate<algm-EnggCalibrate>` algorithm, and visualise them.
+It also allows for the loading of GSAS parameter files created by the calibration process
+to load a previously created calibration into the interface.
 
 When loading an existing calibration, the fields for creating a new calibration will be
 automatically filled, allowing the recreation of the workspaces and plots generated by
@@ -52,14 +60,18 @@ Creating a new calibration file generates 3 GSAS instrument parameter files,
 one covering all banks and separate ones for each individual bank. All 3 files are written
 to the same directory:
 
-`Engineering_Mantid/Calibration/`
+`<CHOSEN_OUTPUT_DIRECTORY>/Calibration/`
 
 If an RB number has been specified the files will also be saved to a user directory
 in the base directory:
 
-`Engineering_Mantid/User/RBNumber/Calibration/`
+`<CHOSEN_OUTPUT_DIRECTORY>/User/RBNumber/Calibration/`
+
+Cropping
+^^^^^^^^
 
-Currently, the `Engineering_Mantid` directory is created in the current user's home directory.
+The interface also provides the ability to restrict a new calibration to one of the two banks
+or to a custom list of spectra.
 
 Parameters
 ^^^^^^^^^^
@@ -74,6 +86,13 @@ Calibration Sample Number
 Path
     The path to the GSAS parameter file to be loaded.
 
+Bank/Spectra
+    Select a bank to crop to or specify a custom spectra will be entered.
+
+Custom Spectra
+    A comma separated list of spectra to restrict the calibration to. Can be provided as single spectrum numbers
+    or ranges using hyphens (e.g. 14-150, 405, 500-600).
+
 Focus
 -----
 
diff --git a/scripts/Engineering/EnggUtils.py b/scripts/Engineering/EnggUtils.py
index 803fc2261dbf676bc9f6fbbf6adb2af52e7448a9..441f7f14a3647ce6962de1b2571915530bd2a57e 100644
--- a/scripts/Engineering/EnggUtils.py
+++ b/scripts/Engineering/EnggUtils.py
@@ -328,10 +328,9 @@ def crop_data(parent, ws, indices):
     @returns cropped workspace, with only the spectra corresponding to the indices requested
     """
     # Leave only spectra between min and max
-    alg = parent.createChildAlgorithm('CropWorkspace')
+    alg = parent.createChildAlgorithm('ExtractSpectra')
     alg.setProperty('InputWorkspace', ws)
-    alg.setProperty('StartWorkspaceIndex', min(indices))
-    alg.setProperty('EndWorkspaceIndex', max(indices))
+    alg.setProperty('WorkspaceIndexList', indices)
     alg.execute()
 
     return alg.getProperty('OutputWorkspace').value
diff --git a/scripts/Engineering/gui/CMakeLists.txt b/scripts/Engineering/gui/CMakeLists.txt
index 62c1f5271a99bb83e2417cbef8c7104fb40003d1..bb3f87d2b5ec376aaf81cf9abd3be8966a1942f4 100644
--- a/scripts/Engineering/gui/CMakeLists.txt
+++ b/scripts/Engineering/gui/CMakeLists.txt
@@ -3,6 +3,9 @@
 set(TEST_PY_FILES
     # Common
     engineering_diffraction/tabs/common/test/test_vanadium_corrections.py
+    # Cropping
+    engineering_diffraction/tabs/common/cropping/test/test_cropping_model.py
+    engineering_diffraction/tabs/common/cropping/test/test_cropping_presenter.py
     # Settings
     engineering_diffraction/settings/test/test_settings_helper.py
     engineering_diffraction/settings/test/test_settings_model.py
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/calibration_tab.ui b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/calibration_tab.ui
index 6fc3ef07bb6b5f98925cbd3d12d8e0837c69fbba..2f8e4fee2c7fd19560f0d6d89df2db6cd1c0a33c 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/calibration_tab.ui
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/calibration_tab.ui
@@ -9,8 +9,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>618</width>
-    <height>192</height>
+    <width>661</width>
+    <height>236</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -73,52 +73,44 @@ QGroupBox:title {
        </property>
        <layout class="QGridLayout" name="gridLayout_2">
         <item row="1" column="0">
-         <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0" columnstretch="0,0,0">
+         <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0,0,0,0,0,0,0" columnstretch="0,0,0">
           <property name="sizeConstraint">
            <enum>QLayout::SetDefaultConstraint</enum>
           </property>
           <property name="verticalSpacing">
            <number>6</number>
           </property>
-          <item row="4" column="0" colspan="3">
-           <widget class="QRadioButton" name="radio_loadCalib">
-            <property name="text">
-             <string>Load Existing Calibration</string>
-            </property>
-            <property name="checked">
-             <bool>false</bool>
-            </property>
-           </widget>
-          </item>
-          <item row="7" column="0" colspan="2">
-           <widget class="QCheckBox" name="check_plotOutput">
-            <property name="enabled">
-             <bool>true</bool>
+          <item row="6" column="0" colspan="3">
+           <widget class="Line" name="line_crop">
+            <property name="minimumSize">
+             <size>
+              <width>0</width>
+              <height>0</height>
+             </size>
             </property>
-            <property name="text">
-             <string>Plot Calibrated Workspace</string>
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
             </property>
            </widget>
           </item>
-          <item row="0" column="0" colspan="3">
-           <widget class="QRadioButton" name="radio_newCalib">
-            <property name="text">
-             <string>Create New Calibration</string>
-            </property>
-            <property name="checked">
-             <bool>true</bool>
+          <item row="5" column="0" colspan="3">
+           <widget class="FileFinder" name="finder_path" native="true">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
             </property>
-           </widget>
-          </item>
-          <item row="6" column="0" colspan="3">
-           <widget class="Line" name="line">
-            <property name="orientation">
-             <enum>Qt::Horizontal</enum>
+            <property name="minimumSize">
+             <size>
+              <width>0</width>
+              <height>0</height>
+             </size>
             </property>
            </widget>
           </item>
           <item row="3" column="0" colspan="3">
-           <spacer name="verticalSpacer">
+           <spacer name="spacer_new_exist">
             <property name="orientation">
              <enum>Qt::Vertical</enum>
             </property>
@@ -133,16 +125,6 @@ QGroupBox:title {
             </property>
            </spacer>
           </item>
-          <item row="7" column="2">
-           <widget class="QPushButton" name="button_calibrate">
-            <property name="enabled">
-             <bool>true</bool>
-            </property>
-            <property name="text">
-             <string>Calibrate</string>
-            </property>
-           </widget>
-          </item>
           <item row="1" column="0" colspan="3">
            <widget class="FileFinder" name="finder_vanadium" native="true">
             <property name="minimumSize">
@@ -153,6 +135,16 @@ QGroupBox:title {
             </property>
            </widget>
           </item>
+          <item row="0" column="0" colspan="3">
+           <widget class="QRadioButton" name="radio_newCalib">
+            <property name="text">
+             <string>Create New Calibration</string>
+            </property>
+            <property name="checked">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
           <item row="2" column="0" colspan="3">
            <widget class="FileFinder" name="finder_sample" native="true">
             <property name="sizePolicy">
@@ -169,14 +161,52 @@ QGroupBox:title {
             </property>
            </widget>
           </item>
-          <item row="5" column="0" colspan="3">
-           <widget class="FileFinder" name="finder_path" native="true">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
+          <item row="10" column="0" colspan="2">
+           <widget class="QCheckBox" name="check_plotOutput">
+            <property name="enabled">
+             <bool>true</bool>
+            </property>
+            <property name="text">
+             <string>Plot Calibrated Workspace</string>
+            </property>
+           </widget>
+          </item>
+          <item row="7" column="0" colspan="3">
+           <widget class="QCheckBox" name="check_cropCalib">
+            <property name="text">
+             <string>Crop Calibration</string>
+            </property>
+           </widget>
+          </item>
+          <item row="10" column="2">
+           <widget class="QPushButton" name="button_calibrate">
+            <property name="enabled">
+             <bool>true</bool>
+            </property>
+            <property name="text">
+             <string>Calibrate</string>
+            </property>
+           </widget>
+          </item>
+          <item row="4" column="0" colspan="3">
+           <widget class="QRadioButton" name="radio_loadCalib">
+            <property name="text">
+             <string>Load Existing Calibration</string>
             </property>
+            <property name="checked">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="9" column="0" colspan="3">
+           <widget class="Line" name="line_go">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+           </widget>
+          </item>
+          <item row="8" column="0" colspan="3">
+           <widget class="CroppingView" name="widget_cropping" native="true">
             <property name="minimumSize">
              <size>
               <width>0</width>
@@ -191,7 +221,7 @@ QGroupBox:title {
       </widget>
      </item>
      <item>
-      <spacer name="verticalSpacer_2">
+      <spacer name="spacer_bottom">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
@@ -214,6 +244,12 @@ QGroupBox:title {
    <header>mantidqt.widgets.filefinder</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>CroppingView</class>
+   <extends>QWidget</extends>
+   <header>Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_view</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <tabstops>
   <tabstop>radio_newCalib</tabstop>
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/model.py
index 24357c27e2aae6f9594e6d75e3eb6cf146cdae74..fd3ee96f23bc94fcc8b596fb9a2f81e6013ee224 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/model.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/model.py
@@ -15,6 +15,7 @@ from mantid.api import AnalysisDataService as Ads
 from mantid.kernel import logger
 from mantid.simpleapi import EnggCalibrate, DeleteWorkspace, CloneWorkspace, \
     CreateWorkspace, AppendSpectra, CreateEmptyTableWorkspace
+from mantidqt.plotting.functions import plot
 from Engineering.EnggUtils import write_ENGINX_GSAS_iparam_file
 from Engineering.gui.engineering_diffraction.tabs.common import vanadium_corrections
 from Engineering.gui.engineering_diffraction.tabs.common import path_handling
@@ -35,7 +36,9 @@ class CalibrationModel(object):
                                sample_path,
                                plot_output,
                                instrument,
-                               rb_num=None):
+                               rb_num=None,
+                               bank=None,
+                               spectrum_numbers=None):
         """
         Create a new calibration from a vanadium run and sample run
         :param vanadium_path: Path to vanadium data file.
@@ -43,6 +46,8 @@ class CalibrationModel(object):
         :param plot_output: Whether the output should be plotted.
         :param instrument: The instrument the data relates to.
         :param rb_num: The RB number for file creation.
+        :param bank: Optional parameter to crop by bank
+        :param spectrum_numbers: Optional parameter to crop using spectrum numbers.
         """
         van_integration, van_curves = vanadium_corrections.fetch_correction_workspaces(
             vanadium_path, instrument, rb_num=rb_num)
@@ -51,31 +56,50 @@ class CalibrationModel(object):
                                       path_handling.ENGINEERING_PREFIX, "full_calibration")
         if full_calib_path is not None and path.exists(full_calib_path):
             full_calib = path_handling.load_workspace(full_calib_path)
-            output = self.run_calibration(sample_workspace, van_integration, van_curves, full_calib_ws=full_calib)
+            output = self.run_calibration(sample_workspace,
+                                          van_integration,
+                                          van_curves,
+                                          bank,
+                                          spectrum_numbers,
+                                          full_calib_ws=full_calib)
         else:
-            output = self.run_calibration(sample_workspace, van_integration, van_curves)
+            output = self.run_calibration(sample_workspace, van_integration, van_curves, bank,
+                                          spectrum_numbers)
         if plot_output:
             self._plot_vanadium_curves()
-            for i in range(2):
-                difc = [output[i].DIFC]
-                tzero = [output[i].TZERO]
-                self._generate_difc_tzero_workspace(difc, tzero, i + 1)
-            self._plot_difc_tzero()
-        difc = [output[0].DIFC, output[1].DIFC]
-        tzero = [output[0].TZERO, output[1].TZERO]
+            for i in range(len(output)):
+                if spectrum_numbers:
+                    bank_name = "cropped"
+                elif bank is None:
+                    bank_name = str(i + 1)
+                else:
+                    bank_name = bank
+                difc = output[i].DIFC
+                tzero = output[i].TZERO
+                self._generate_difc_tzero_workspace(difc, tzero, bank_name)
+            if bank is None and spectrum_numbers is None:
+                self._plot_difc_tzero()
+            elif spectrum_numbers is None:
+                self._plot_difc_tzero_single_bank_or_custom(bank)
+            else:
+                self._plot_difc_tzero_single_bank_or_custom("cropped")
+        difc = [i.DIFC for i in output]
+        tzero = [i.TZERO for i in output]
 
         params_table = []
-        for i in range(2):
+
+        for i in range(len(difc)):
             params_table.append([i, difc[i], 0.0, tzero[i]])
         self.update_calibration_params_table(params_table)
 
         calib_dir = path.join(path_handling.get_output_path(), "Calibration", "")
-        self.create_output_files(calib_dir, difc, tzero, sample_path, vanadium_path, instrument)
+        self.create_output_files(calib_dir, difc, tzero, sample_path, vanadium_path, instrument,
+                                 bank, spectrum_numbers)
         if rb_num:
             user_calib_dir = path.join(path_handling.get_output_path(), "User", rb_num,
                                        "Calibration", "")
             self.create_output_files(user_calib_dir, difc, tzero, sample_path, vanadium_path,
-                                     instrument)
+                                     instrument, bank, spectrum_numbers)
 
     def load_existing_gsas_parameters(self, file_path):
         if not path.exists(file_path):
@@ -139,14 +163,14 @@ class CalibrationModel(object):
 
     @staticmethod
     def _generate_difc_tzero_workspace(difc, tzero, bank):
-        bank_ws = Ads.retrieve(CalibrationModel._generate_table_workspace_name(bank - 1))
+        bank_ws = Ads.retrieve(CalibrationModel._generate_table_workspace_name(bank))
 
         x_val = []
         y_val = []
         y2_val = []
 
-        difc_to_plot = difc[0]
-        tzero_to_plot = tzero[0]
+        difc_to_plot = difc
+        tzero_to_plot = tzero
 
         for irow in range(0, bank_ws.rowCount()):
             x_val.append(bank_ws.cell(irow, 0))
@@ -185,35 +209,70 @@ class CalibrationModel(object):
             ax.set_xlabel("Expected Peaks Centre(dSpacing, A)")
         fig.show()
 
-    def run_calibration(self, sample_ws, van_integration, van_curves, full_calib_ws=None):
+    @staticmethod
+    def _plot_difc_tzero_single_bank_or_custom(bank):
+        bank_ws = Ads.retrieve("engggui_difc_zero_peaks_bank_" + str(bank))
+
+        ax = plot([bank_ws], [0, 1],
+                  plot_kwargs={
+                      "linestyle": "--",
+                      "marker": "o",
+                      "markersize": "3"
+                  }).gca()
+        ax.set_title("Engg Gui Difc Zero Peaks Bank " + str(bank))
+        ax.legend(("Peaks Fitted", "DifC/TZero Fitted Straight Line"))
+        ax.set_xlabel("Expected Peaks Centre(dSpacing, A)")
+
+    def run_calibration(self,
+                        sample_ws,
+                        van_integration,
+                        van_curves,
+                        bank,
+                        spectrum_numbers,
+                        full_calib_ws=None):
         """
         Runs the main Engineering calibration algorithm.
         :param sample_ws: The workspace with the sample data.
         :param van_integration: The integration values from the vanadium corrections
         :param van_curves: The curves from the vanadium corrections.
         :param full_calib_ws: Full pixel calibration of the detector (optional)
+        :param bank: The bank to crop to, both if none.
+        :param spectrum_numbers: The spectrum numbers to crop to, no crop if none.
         :return: The output of the algorithm.
         """
-        output = [None] * 2
-        for i in range(2):
-            table_name = self._generate_table_workspace_name(i)
-            if full_calib_ws is not None:
-                output[i] = EnggCalibrate(InputWorkspace=sample_ws,
-                                          VanIntegrationWorkspace=van_integration,
-                                          VanCurvesWorkspace=van_curves,
-                                          Bank=str(i + 1),
-                                          FittedPeaks=table_name)
+        kwargs = {
+            "InputWorkspace": sample_ws,
+            "VanIntegrationWorkspace": van_integration,
+            "VanCurvesWorkspace": van_curves
+        }
+
+        def run_engg_calibrate(kwargs_to_pass):
+            return EnggCalibrate(**kwargs_to_pass)
+
+        if full_calib_ws is not None:
+            kwargs["DetectorPositions"] = full_calib_ws
+        if spectrum_numbers is None:
+            if bank is None:
+                output = [None] * 2
+                for i in range(len(output)):
+                    kwargs["Bank"] = str(i+1)
+                    kwargs["FittedPeaks"] = self._generate_table_workspace_name(str(i+1))
+                    output[i] = run_engg_calibrate(kwargs)
             else:
-                output[i] = EnggCalibrate(InputWorkspace=sample_ws,
-                                          VanIntegrationWorkspace=van_integration,
-                                          VanCurvesWorkspace=van_curves,
-                                          Bank=str(i + 1),
-                                          FittedPeaks=table_name,
-                                          DetectorPositions=full_calib_ws)
+                output = [None]
+                kwargs["Bank"] = bank
+                kwargs["FittedPeaks"] = self._generate_table_workspace_name(bank)
+                output[0] = run_engg_calibrate(kwargs)
+
+        else:
+            output = [None]
+            kwargs["SpectrumNumbers"] = spectrum_numbers
+            kwargs["FittedPeaks"] = self._generate_table_workspace_name("cropped")
+            output[0] = run_engg_calibrate(kwargs)
         return output
 
     def create_output_files(self, calibration_dir, difc, tzero, sample_path, vanadium_path,
-                            instrument):
+                            instrument, bank, spectrum_numbers):
         """
         Create output files from the algorithms in the specified directory
         :param calibration_dir: The directory to save the files into.
@@ -222,36 +281,42 @@ class CalibrationModel(object):
         :param sample_path: The path to the sample data file.
         :param vanadium_path: The path to the vanadium data file.
         :param instrument: The instrument (ENGINX or IMAT)
+        :param bank: Optional parameter to crop by bank
+        :param spectrum_numbers: Optional parameter to crop using spectrum numbers.
         """
+        kwargs = {"ceria_run": sample_path, "vanadium_run": vanadium_path}
+
+        def south_kwargs():
+            kwargs["template_file"] = SOUTH_BANK_TEMPLATE_FILE
+            kwargs["bank_names"] = ["South"]
+
+        def north_kwargs():
+            kwargs["template_file"] = NORTH_BANK_TEMPLATE_FILE
+            kwargs["bank_names"] = ["North"]
+
+        def generate_output_file(difc_list, tzero_list, bank_name, kwargs_to_pass):
+            file_path = calibration_dir + self._generate_output_file_name(vanadium_path, sample_path, instrument,
+                                                                          bank=bank_name)
+            write_ENGINX_GSAS_iparam_file(file_path, difc_list, tzero_list, **kwargs_to_pass)
+
         if not path.exists(calibration_dir):
             makedirs(calibration_dir)
-        filename = self._generate_output_file_name(vanadium_path,
-                                                   sample_path,
-                                                   instrument,
-                                                   bank="all")
-        # Both Banks
-        file_path = calibration_dir + filename
-        write_ENGINX_GSAS_iparam_file(file_path,
-                                      difc,
-                                      tzero,
-                                      ceria_run=sample_path,
-                                      vanadium_run=vanadium_path)
-        # North Bank
-        file_path = calibration_dir + self._generate_output_file_name(
-            vanadium_path, sample_path, instrument, bank="north")
-        write_ENGINX_GSAS_iparam_file(file_path, [difc[0]], [tzero[0]],
-                                      ceria_run=sample_path,
-                                      vanadium_run=vanadium_path,
-                                      template_file=NORTH_BANK_TEMPLATE_FILE,
-                                      bank_names=["North"])
-        # South Bank
-        file_path = calibration_dir + self._generate_output_file_name(
-            vanadium_path, sample_path, instrument, bank="south")
-        write_ENGINX_GSAS_iparam_file(file_path, [difc[1]], [tzero[1]],
-                                      ceria_run=sample_path,
-                                      vanadium_run=vanadium_path,
-                                      template_file=SOUTH_BANK_TEMPLATE_FILE,
-                                      bank_names=["South"])
+
+        if bank is None and spectrum_numbers is None:
+            generate_output_file(difc, tzero, "all", kwargs)
+            north_kwargs()
+            generate_output_file([difc[0]], [tzero[0]], "north", kwargs)
+            south_kwargs()
+            generate_output_file([difc[1]], [tzero[1]], "south", kwargs)
+        elif bank == "1":
+            north_kwargs()
+            generate_output_file([difc[0]], [tzero[0]], "north", kwargs)
+        elif bank == "2":
+            south_kwargs()
+            generate_output_file([difc[0]], [tzero[0]], "south", kwargs)
+        elif bank is None:  # Custom cropped files use the north bank template.
+            north_kwargs()
+            generate_output_file([difc[0]], [tzero[0]], "cropped", kwargs)
 
     @staticmethod
     def get_info_from_file(file_path):
@@ -284,7 +349,7 @@ class CalibrationModel(object):
 
     @staticmethod
     def _generate_table_workspace_name(bank_num):
-        return "engggui_calibration_bank_" + str(bank_num + 1)
+        return "engggui_calibration_bank_" + str(bank_num)
 
     @staticmethod
     def _generate_output_file_name(vanadium_path, sample_path, instrument, bank):
@@ -305,6 +370,8 @@ class CalibrationModel(object):
             filename = filename + "bank_North.prm"
         elif bank == "south":
             filename = filename + "bank_South.prm"
+        elif bank == "cropped":
+            filename = filename + "cropped.prm"
         else:
             raise ValueError("Invalid bank name entered")
         return filename
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/presenter.py
index d7b7cb19acf49c3991730b684a7dec859cfce762..7e81c200e4bd9d60716ecd7abfa8cbe8700d26ca 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/presenter.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/presenter.py
@@ -11,6 +11,8 @@ from copy import deepcopy
 
 from Engineering.gui.engineering_diffraction.tabs.common import INSTRUMENT_DICT, create_error_message
 from Engineering.gui.engineering_diffraction.tabs.common.calibration_info import CalibrationInfo
+from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_widget import CroppingWidget
+
 from mantidqt.utils.asynchronous import AsyncTask
 from mantid.simpleapi import logger
 from mantidqt.utils.observer_pattern import Observable
@@ -26,23 +28,33 @@ class CalibrationPresenter(object):
         self.current_calibration = CalibrationInfo()
         self.pending_calibration = CalibrationInfo()
 
-        # Connect view signals to local functions.
+        self.connect_view_signals()
+
+        # Main Window State Variables
+        self.instrument = "ENGINX"
+        self.rb_num = None
+
+        # Cropping Options
+        self.cropping_widget = CroppingWidget(self.view, view=self.view.get_cropping_widget())
+        self.view.set_cropping_widget_hidden()
+
+    def connect_view_signals(self):
         self.view.set_on_calibrate_clicked(self.on_calibrate_clicked)
         self.view.set_enable_controls_connection(self.set_calibrate_controls_enabled)
         self.view.set_update_fields_connection(self.set_field_values)
         self.view.set_on_radio_new_toggled(self.set_create_new_enabled)
         self.view.set_on_radio_existing_toggled(self.set_load_existing_enabled)
-
-        # Main Window State Variables
-        self.instrument = "ENGINX"
-        self.rb_num = None
+        self.view.set_on_check_cropping_state_changed(self.show_cropping)
 
     def on_calibrate_clicked(self):
         plot_output = self.view.get_plot_output()
         if self.view.get_new_checked() and self._validate():
             vanadium_file = self.view.get_vanadium_filename()
             sample_file = self.view.get_sample_filename()
-            self.start_calibration_worker(vanadium_file, sample_file, plot_output, self.rb_num)
+            if self.view.get_crop_checked():
+                self.start_cropped_calibration_worker(vanadium_file, sample_file, plot_output, self.rb_num)
+            else:
+                self.start_calibration_worker(vanadium_file, sample_file, plot_output, self.rb_num)
         elif self.view.get_load_checked():
             if not self.validate_path():
                 return
@@ -52,25 +64,39 @@ class CalibrationPresenter(object):
             self.pending_calibration.set_calibration(vanadium_file, sample_file, instrument)
             self.set_current_calibration()
 
-    def start_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num):
+    def start_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num, bank=None,
+                                 spectrum_numbers=None):
         """
         Calibrate the data in a separate thread so as to not freeze the GUI.
         :param vanadium_path: Path to vanadium data file.
         :param sample_path: Path to sample data file.
         :param plot_output: Whether to plot the output.
         :param rb_num: The current RB number set in the GUI.
+        :param bank: Optional parameter to crop by bank.
+        :param spectrum_numbers: Optional parameter to crop by spectrum number.
         """
-        self.worker = AsyncTask(self.model.create_new_calibration, (vanadium_path, sample_path), {
-            "plot_output": plot_output,
-            "instrument": self.instrument,
-            "rb_num": rb_num
-        },
+        self.worker = AsyncTask(self.model.create_new_calibration, (vanadium_path, sample_path),
+                                {
+                                "plot_output": plot_output,
+                                "instrument": self.instrument,
+                                "rb_num": rb_num,
+                                "bank": bank,
+                                "spectrum_numbers": spectrum_numbers
+                                },
                                 error_cb=self._on_error,
                                 success_cb=self._on_success)
         self.pending_calibration.set_calibration(vanadium_path, sample_path, self.instrument)
         self.set_calibrate_controls_enabled(False)
         self.worker.start()
 
+    def start_cropped_calibration_worker(self, vanadium_path, sample_path, plot_output, rb_num):
+        if self.cropping_widget.is_custom():
+            spec_nums = self.cropping_widget.get_custom_spectra()
+            self.start_calibration_worker(vanadium_path, sample_path, plot_output, rb_num, spectrum_numbers=spec_nums)
+        else:
+            bank = self.cropping_widget.get_bank()
+            self.start_calibration_worker(vanadium_path, sample_path, plot_output, rb_num, bank=bank)
+
     def set_current_calibration(self, success_info=None):
         if success_info:
             logger.information("Thread executed in " + str(success_info.elapsed_time) + " seconds.")
@@ -99,6 +125,9 @@ class CalibrationPresenter(object):
         if not self.validate_run_numbers():
             create_error_message(self.view, "Check run numbers/path is valid.")
             return False
+        if not self.cropping_widget.is_valid():
+            create_error_message(self.view, "Check cropping values are valid.")
+            return False
         return True
 
     def validate_run_numbers(self):
@@ -131,6 +160,7 @@ class CalibrationPresenter(object):
         if enabled:
             self.set_calibrate_button_text("Calibrate")
             self.view.set_check_plot_output_enabled(True)
+            self.view.set_check_cropping_enabled(True)
             self.find_files()
 
     def set_load_existing_enabled(self, enabled):
@@ -138,6 +168,8 @@ class CalibrationPresenter(object):
         if enabled:
             self.set_calibrate_button_text("Load")
             self.view.set_check_plot_output_enabled(False)
+            self.view.set_check_cropping_enabled(False)
+            self.view.set_check_cropping_state(0)
 
     def set_calibrate_button_text(self, text):
         self.view.set_calibrate_button_text(text)
@@ -146,6 +178,12 @@ class CalibrationPresenter(object):
         self.view.find_sample_files()
         self.view.find_vanadium_files()
 
+    def show_cropping(self, show):
+        if show:
+            self.view.set_cropping_widget_visible()
+        else:
+            self.view.set_cropping_widget_hidden()
+
     # -----------------------
     # Observers / Observables
     # -----------------------
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_model.py
index e5ea4fde9e1c83df09746247076e47c28e0698f1..87fc73d965c4855f9b774b8054fe612be9894c54 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_model.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_model.py
@@ -10,6 +10,7 @@ from __future__ import (absolute_import, division, print_function)
 import unittest
 
 from mantid.py3compat.mock import patch
+from mantid.py3compat.mock import MagicMock
 from Engineering.gui.engineering_diffraction.tabs.calibration.model import CalibrationModel
 
 VANADIUM_NUMBER = "307521"
@@ -60,14 +61,18 @@ class CalibrationModelTest(unittest.TestCase):
     @patch(file_path + ".path_handling.load_workspace")
     @patch(class_path + '.run_calibration')
     @patch(file_path + '.vanadium_corrections.fetch_correction_workspaces')
-    def test_having_full_calib_set_uses_file(self, van_corr, calibrate_alg, load_workspace, output_files,
-                                             update_table, setting, path):
+    def test_having_full_calib_set_uses_file(self, van_corr, calibrate_alg, load_workspace,
+                                             output_files, update_table, setting, path):
         path.return_value = True
         setting.return_value = "mocked/out/path"
         van_corr.return_value = ("mocked_integration", "mocked_curves")
         load_workspace.return_value = "mocked_workspace"
         self.model.create_new_calibration(VANADIUM_NUMBER, CERIUM_NUMBER, False, "ENGINX")
-        calibrate_alg.assert_called_with("mocked_workspace", "mocked_integration", "mocked_curves",
+        calibrate_alg.assert_called_with("mocked_workspace",
+                                         "mocked_integration",
+                                         "mocked_curves",
+                                         None,
+                                         None,
                                          full_calib_ws="mocked_workspace")
 
     @patch(class_path + '.update_calibration_params_table')
@@ -80,6 +85,7 @@ class CalibrationModelTest(unittest.TestCase):
     @patch(class_path + '.run_calibration')
     def test_plotting_check(self, calib, plot_difc_zero, gen_difc, plot_van, van, sample,
                             output_files, update_table):
+        calib.return_value = [MagicMock(), MagicMock()]
         van.return_value = ("A", "B")
         self.model.create_new_calibration(VANADIUM_NUMBER, CERIUM_NUMBER, False, "ENGINX")
         plot_van.assert_not_called()
@@ -90,6 +96,30 @@ class CalibrationModelTest(unittest.TestCase):
         self.assertEqual(gen_difc.call_count, 2)
         self.assertEqual(plot_difc_zero.call_count, 1)
 
+    @patch(class_path + '.update_calibration_params_table')
+    @patch(class_path + '.create_output_files')
+    @patch(file_path + ".path_handling.load_workspace")
+    @patch(file_path + '.vanadium_corrections.fetch_correction_workspaces')
+    @patch(class_path + '._plot_vanadium_curves')
+    @patch(class_path + '._generate_difc_tzero_workspace')
+    @patch(class_path + '._plot_difc_tzero')
+    @patch(class_path + '._plot_difc_tzero_single_bank_or_custom')
+    @patch(class_path + '.run_calibration')
+    def test_plotting_check_cropped(self, calib, plot_difc_zero_cus, plot_difc_zero, gen_difc,
+                                    plot_van, van, sample, output_files, update_table):
+        calib.return_value = [MagicMock()]
+        van.return_value = ("A", "B")
+        self.model.create_new_calibration(VANADIUM_NUMBER, CERIUM_NUMBER, False, "ENGINX")
+        plot_van.assert_not_called()
+        plot_difc_zero_cus.assert_not_called()
+        plot_difc_zero.assert_not_called()
+        gen_difc.assert_not_called()
+        self.model.create_new_calibration(VANADIUM_NUMBER, CERIUM_NUMBER, True, "ENGINX", bank=1)
+        plot_van.assert_called_once()
+        self.assertEqual(gen_difc.call_count, 1)
+        plot_difc_zero.assert_not_called()
+        self.assertEqual(plot_difc_zero_cus.call_count, 1)
+
     @patch(class_path + '.update_calibration_params_table')
     @patch(class_path + '.create_output_files')
     @patch(file_path + ".path_handling.load_workspace")
@@ -144,8 +174,12 @@ class CalibrationModelTest(unittest.TestCase):
         filename = "output"
         output_name.return_value = filename
 
-        self.model.create_output_files("test/", [0, 0], [1, 1], sample_path, vanadium_path,
-                                       "ENGINX")
+        self.model.create_output_files("test/", [0, 0], [1, 1],
+                                       sample_path,
+                                       vanadium_path,
+                                       "ENGINX",
+                                       bank=None,
+                                       spectrum_numbers=None)
 
         self.assertEqual(make_dirs.call_count, 1)
         self.assertEqual(write_file.call_count, 3)
@@ -157,7 +191,7 @@ class CalibrationModelTest(unittest.TestCase):
 
     def test_generate_table_workspace_name(self):
         self.assertEqual(self.model._generate_table_workspace_name(20),
-                         "engggui_calibration_bank_21")
+                         "engggui_calibration_bank_20")
 
     def test_generate_output_file_name_for_north_bank(self):
         filename = self.model._generate_output_file_name("test/20.raw", "test/10.raw", "ENGINX",
@@ -174,6 +208,11 @@ class CalibrationModelTest(unittest.TestCase):
                                                          "all")
         self.assertEqual(filename, "ENGINX_20_10_all_banks.prm")
 
+    def test_generate_output_file_name_for_cropped_bank(self):
+        filename = self.model._generate_output_file_name("test/20.raw", "test/10.raw", "ENGINX",
+                                                         "cropped")
+        self.assertEqual(filename, "ENGINX_20_10_cropped.prm")
+
     def test_generate_output_file_name_for_invalid_bank(self):
         self.assertRaises(ValueError, self.model._generate_output_file_name, "test/20.raw",
                           "test/10.raw", "ENGINX", "INVALID")
@@ -203,6 +242,91 @@ class CalibrationModelTest(unittest.TestCase):
 
         self.assertEqual(ads.retrieve.call_count, 0)
 
+    @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate")
+    def test_run_calibration_no_bank_no_spec_nums_no_full_calib(self, alg):
+        self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", None, None)
+
+        alg.assert_any_call(InputWorkspace="sample",
+                            VanIntegrationWorkspace="vanadium_int",
+                            VanCurvesWorkspace="vanadium_curves",
+                            Bank="1",
+                            FittedPeaks="engggui_calibration_bank_1")
+        alg.assert_any_call(InputWorkspace="sample",
+                            VanIntegrationWorkspace="vanadium_int",
+                            VanCurvesWorkspace="vanadium_curves",
+                            Bank="2",
+                            FittedPeaks="engggui_calibration_bank_2")
+        self.assertEqual(2, alg.call_count)
+
+    @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate")
+    def test_run_calibration_no_bank_no_spec_nums_full_calib(self, alg):
+        self.model.run_calibration("sample",
+                                   "vanadium_int",
+                                   "vanadium_curves",
+                                   None,
+                                   None,
+                                   full_calib_ws="full")
+
+        alg.assert_any_call(InputWorkspace="sample",
+                            VanIntegrationWorkspace="vanadium_int",
+                            VanCurvesWorkspace="vanadium_curves",
+                            Bank="1",
+                            FittedPeaks="engggui_calibration_bank_1",
+                            DetectorPositions="full")
+        alg.assert_any_call(InputWorkspace="sample",
+                            VanIntegrationWorkspace="vanadium_int",
+                            VanCurvesWorkspace="vanadium_curves",
+                            Bank="2",
+                            FittedPeaks="engggui_calibration_bank_2",
+                            DetectorPositions="full")
+        self.assertEqual(2, alg.call_count)
+
+    @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate")
+    def test_run_calibration_bank_no_spec_nums_no_full_calib(self, alg):
+        self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", "1", None)
+
+        alg.assert_any_call(InputWorkspace="sample",
+                            VanIntegrationWorkspace="vanadium_int",
+                            VanCurvesWorkspace="vanadium_curves",
+                            Bank="1",
+                            FittedPeaks="engggui_calibration_bank_1")
+        self.assertEqual(1, alg.call_count)
+
+    @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate")
+    def test_run_calibration_no_bank_spec_nums_no_full_calib(self, alg):
+        self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", None, "1-5, 45-102")
+
+        alg.assert_any_call(InputWorkspace="sample",
+                            VanIntegrationWorkspace="vanadium_int",
+                            VanCurvesWorkspace="vanadium_curves",
+                            SpectrumNumbers="1-5, 45-102",
+                            FittedPeaks="engggui_calibration_bank_cropped")
+        self.assertEqual(1, alg.call_count)
+
+    @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate")
+    def test_run_calibration_bank_no_spec_nums_full_calib(self, alg):
+        self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", "1", None, full_calib_ws="full")
+
+        alg.assert_any_call(InputWorkspace="sample",
+                            VanIntegrationWorkspace="vanadium_int",
+                            VanCurvesWorkspace="vanadium_curves",
+                            Bank="1",
+                            FittedPeaks="engggui_calibration_bank_1",
+                            DetectorPositions="full")
+        self.assertEqual(1, alg.call_count)
+
+    @patch("Engineering.gui.engineering_diffraction.tabs.calibration.model.EnggCalibrate")
+    def test_run_calibration_no_bank_spec_nums_full_calib(self, alg):
+        self.model.run_calibration("sample", "vanadium_int", "vanadium_curves", None, "45-102", full_calib_ws="full")
+
+        alg.assert_any_call(InputWorkspace="sample",
+                            VanIntegrationWorkspace="vanadium_int",
+                            VanCurvesWorkspace="vanadium_curves",
+                            SpectrumNumbers="45-102",
+                            FittedPeaks="engggui_calibration_bank_cropped",
+                            DetectorPositions="full")
+        self.assertEqual(1, alg.call_count)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_presenter.py
index 3c9d9d67968e0b6230d61575d45a2243c6e5d775..0590fbb07bc0a7c9a39640397ee4ad699ce61cfb 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_presenter.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/test/test_calib_presenter.py
@@ -21,10 +21,13 @@ class CalibrationPresenterTest(unittest.TestCase):
     def setUp(self):
         self.view = mock.create_autospec(view.CalibrationView)
         self.model = mock.create_autospec(model.CalibrationModel)
+        self.view.get_cropping_widget.return_value = MagicMock()
         self.presenter = presenter.CalibrationPresenter(self.model, self.view)
+        self.presenter.cropping_widget = MagicMock()
 
     @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker")
     def test_worker_started_with_right_params(self, worker_method):
+        self.view.get_crop_checked.return_value = False
         self.view.get_vanadium_filename.return_value = "307521"
         self.view.get_sample_filename.return_value = "305738"
         self.view.get_plot_output.return_value = True
@@ -33,6 +36,32 @@ class CalibrationPresenterTest(unittest.TestCase):
         self.presenter.on_calibrate_clicked()
         worker_method.assert_called_with("307521", "305738", True, None)
 
+    @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker")
+    def test_worker_started_with_right_params_crop_bank(self, worker_method):
+        self.view.get_crop_checked.return_value = True
+        self.view.get_vanadium_filename.return_value = "307521"
+        self.view.get_sample_filename.return_value = "305738"
+        self.view.get_plot_output.return_value = True
+        self.view.is_searching.return_value = False
+        self.presenter.cropping_widget.is_custom.return_value = False
+        self.presenter.cropping_widget.get_bank.return_value = "bank"
+
+        self.presenter.on_calibrate_clicked()
+        worker_method.assert_called_with("307521", "305738", True, None, bank="bank")
+
+    @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker")
+    def test_worker_started_with_right_params_crop_spec_nums(self, worker_method):
+        self.view.get_crop_checked.return_value = True
+        self.view.get_vanadium_filename.return_value = "307521"
+        self.view.get_sample_filename.return_value = "305738"
+        self.view.get_plot_output.return_value = True
+        self.view.is_searching.return_value = False
+        self.presenter.cropping_widget.is_custom.return_value = True
+        self.presenter.cropping_widget.get_custom_spectra.return_value = "1-56,401-809"
+
+        self.presenter.on_calibrate_clicked()
+        worker_method.assert_called_with("307521", "305738", True, None, spectrum_numbers="1-56,401-809")
+
     @patch(tab_path + ".presenter.create_error_message")
     @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker")
     def test_worker_not_started_while_finder_is_searching(self, worker_method, err_msg):
@@ -61,6 +90,22 @@ class CalibrationPresenterTest(unittest.TestCase):
         worker_method.assert_not_called()
         self.assertEqual(err_msg.call_count, 1)
 
+    @patch(tab_path + ".presenter.create_error_message")
+    @patch(tab_path + ".presenter.CalibrationPresenter.validate_run_numbers")
+    @patch(tab_path + ".presenter.CalibrationPresenter.start_calibration_worker")
+    def test_worker_not_started_when_cropping_invalid(self, worker_method, validator, err_msg):
+        self.view.get_vanadium_filename.return_value = "307521"
+        self.view.get_sample_filename.return_value = "305738"
+        self.view.get_plot_output.return_value = True
+        self.view.is_searching.return_value = False
+        self.view.get_load_checked.return_value = False
+        validator.return_value = True
+        self.presenter.cropping_widget.is_valid.return_value = False
+
+        self.presenter.on_calibrate_clicked()
+        worker_method.assert_not_called()
+        self.assertEqual(err_msg.call_count, 1)
+
     def test_controls_disabled_disables_both(self):
         self.presenter.set_calibrate_controls_enabled(False)
 
@@ -233,6 +278,13 @@ class CalibrationPresenterTest(unittest.TestCase):
 
         self.check_calibration_equal(self.presenter.pending_calibration, expected_pending)
 
+    def test_cropping_disabled_when_loading_calib(self):
+        self.presenter.set_load_existing_enabled(True)
+
+        self.assertEqual(self.view.set_cropping_widget_hidden.call_count, 1)
+        self.view.set_check_cropping_enabled.assert_called_with(False)
+        self.view.set_check_cropping_state.assert_called_with(0)
+
     def check_calibration_equal(self, a, b):
         self.assertEqual(a.get_vanadium(), b.get_vanadium())
         self.assertEqual(a.get_sample(), b.get_sample())
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/view.py b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/view.py
index 25a9ece34c6f24bc39b33dc6673b43c3b8565cc0..2d564dddfd51b2a88357e925ed9a68c4c6f96509 100644
--- a/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/view.py
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/calibration/view.py
@@ -58,6 +58,9 @@ class CalibrationView(QtWidgets.QWidget, Ui_calib):
     def set_update_fields_connection(self, slot):
         self.sig_update_fields.connect(slot)
 
+    def set_on_check_cropping_state_changed(self, slot):
+        self.check_cropCalib.stateChanged.connect(slot)
+
     # =================
     # Component Setters
     # =================
@@ -90,6 +93,18 @@ class CalibrationView(QtWidgets.QWidget, Ui_calib):
     def set_calibrate_button_text(self, text):
         self.button_calibrate.setText(text)
 
+    def set_cropping_widget_visible(self):
+        self.widget_cropping.show()
+
+    def set_cropping_widget_hidden(self):
+        self.widget_cropping.hide()
+
+    def set_check_cropping_enabled(self, enabled):
+        self.check_cropCalib.setEnabled(enabled)
+
+    def set_check_cropping_state(self, state):
+        self.check_cropCalib.setCheckState(state)
+
     # =================
     # Component Getters
     # =================
@@ -121,6 +136,12 @@ class CalibrationView(QtWidgets.QWidget, Ui_calib):
     def get_load_checked(self):
         return self.radio_loadCalib.isChecked()
 
+    def get_crop_checked(self):
+        return self.check_cropCalib.isChecked()
+
+    def get_cropping_widget(self):
+        return self.widget_cropping
+
     # =================
     # State Getters
     # =================
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/__init__.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ffcbd9a030f09caa3ecc80a1689d3ba66ecaa1d
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_model.py
@@ -0,0 +1,69 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+
+from __future__ import (absolute_import, division, print_function)
+
+import re
+
+ENGINX_MAX_SPECTRA = 2513
+VALID_PUNCT = [",", " ", "-"]
+SPLITTING_REGEX = ",|-"
+
+
+class CroppingModel(object):
+    def validate_and_clean_spectrum_numbers(self, numbers):
+        numbers = numbers.strip()
+        try:
+            if self.validate_spectrum_numbers(numbers):
+                numbers = self._clean_spectrum_numbers(numbers)
+                return "", numbers
+            else:
+                return "Invalid spectrum numbers entered. Limits are 0-" + str(ENGINX_MAX_SPECTRA), ""
+        except ValueError as e:
+            return str(e), ""
+
+    def validate_spectrum_numbers(self, numbers):
+        if self._validate_numeric_or_valid_punct(numbers):
+            if "-" in numbers or "," in numbers:
+                return self._validate_spectra_list(numbers)
+            else:
+                return self.validate_spectrum(numbers)
+        return False
+
+    @staticmethod
+    def _validate_numeric_or_valid_punct(string):
+        if all(c.isdigit() or c in VALID_PUNCT for c in string):
+            return True
+        else:
+            raise ValueError("Invalid characters entered. Only numeric characters, ',', and '-' are allowed.")
+
+    def _validate_spectra_list(self, numbers):
+        numbers = re.split(SPLITTING_REGEX, numbers)
+        return all(self.validate_spectrum(i) for i in numbers)
+
+    @staticmethod
+    def validate_spectrum(number):
+        number = number.strip()
+        return number.isdigit() and 0 <= int(number) <= ENGINX_MAX_SPECTRA
+
+    def _clean_spectrum_numbers(self, numbers):
+        numbers = [word.strip() for word in numbers.split(",")]
+        return ",".join([self._clean_ranges(i) for i in numbers])
+
+    @staticmethod
+    def _clean_ranges(word):
+        if "-" in word:
+            nums = word.split("-")
+            num1, num2 = (i.strip() for i in nums)
+            if num1 and num2:
+                if int(num1) > int(num2):
+                    word = "-".join([num2, num1])
+                elif int(num1) < int(num2):
+                    word = "-".join([num1, num2])
+                else:  # Not a valid range
+                    raise ValueError("Ranges cannot contain the same value twice. Invalid Range: " + word)
+        return word
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..7481cd493260c5ea6a2f62675b8b807781031c50
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_presenter.py
@@ -0,0 +1,79 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+
+from __future__ import (absolute_import, division, print_function)
+
+
+class CroppingPresenter(object):
+    def __init__(self, model, view):
+        self.model = model
+        self.view = view
+
+        self.bank = 0
+        self.custom_spectra_enabled = False
+        self.custom_spectra = ""
+        self.custom_valid = True
+
+        # Connect view signals to local functions
+        self.view.set_on_combo_changed(self.on_combo_changed)
+        self.view.set_on_custom_spectra_changed(self.on_spectra_changed)
+
+        self.on_combo_changed(0)
+
+    # Signal Activated Functions
+
+    def on_combo_changed(self, index):
+        if index == 0:
+            self.bank = 1
+            self.custom_spectra_enabled = False
+            self.set_custom_visibility(False)
+        elif index == 1:
+            self.bank = 2
+            self.custom_spectra_enabled = False
+            self.set_custom_visibility(False)
+        else:
+            self.bank = 0
+            self.custom_spectra_enabled = True
+            self.set_custom_visibility(True)
+
+    def on_spectra_changed(self, text):
+        error, value = self.model.validate_and_clean_spectrum_numbers(text)
+        if error == "":
+            self.custom_spectra = value
+        self.set_invalid_status(error)
+
+    # Getters
+
+    def get_custom_spectra(self):
+        return self.custom_spectra
+
+    def get_custom_spectra_enabled(self):
+        return self.custom_spectra_enabled
+
+    def get_bank(self):
+        return self.bank
+
+    def is_valid(self):
+        return self.custom_valid
+
+    # Setters
+
+    def set_custom_visibility(self, visible):
+        if visible:
+            self.on_spectra_changed(self.view.get_custom_spectra_text())
+            self.view.set_custom_spectra_entry_visible()
+        else:
+            self.custom_valid = True  # Make custom valid if not used, makes validation easier.
+            self.view.set_custom_spectra_entry_hidden()
+
+    def set_invalid_status(self, text):
+        if text:
+            self.view.set_invalid_indicator_visible(text)
+            self.custom_valid = False
+        else:
+            self.view.set_invalid_indicator_hidden()
+            self.custom_valid = True
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_view.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_view.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a828da157100d26f203547990d97e0314e857f6
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_view.py
@@ -0,0 +1,57 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+
+from __future__ import (absolute_import, division, print_function)
+from qtpy import QtWidgets
+
+from mantidqt.utils.qt import load_ui
+
+Ui_cropping, _ = load_ui(__file__, "cropping_widget.ui")
+
+
+class CroppingView(QtWidgets.QWidget, Ui_cropping):
+    def __init__(self, parent):
+        super(CroppingView, self).__init__(parent)
+        self.setupUi(self)
+        self.widget_custom.hide()
+
+    # =================
+    # Slot Connectors
+    # =================
+
+    def set_on_combo_changed(self, slot):
+        self.combo_bank.currentIndexChanged.connect(slot)
+
+    def set_on_custom_spectra_changed(self, slot):
+        self.edit_custom.textChanged.connect(slot)
+
+    # =================
+    # Component Setters
+    # =================
+
+    def set_custom_spectra_entry_hidden(self):
+        self.widget_custom.hide()
+
+    def set_custom_spectra_entry_visible(self):
+        self.widget_custom.show()
+
+    def set_invalid_indicator_hidden(self):
+        self.label_customValid.hide()
+
+    def set_invalid_indicator_visible(self, string):
+        self.label_customValid.setToolTip(string)
+        self.label_customValid.show()
+
+    # =================
+    # Component Getters
+    # =================
+
+    def get_combo_value(self):
+        return self.combo_bank.currentText()
+
+    def get_custom_spectra_text(self):
+        return self.edit_custom.text()
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.py
new file mode 100644
index 0000000000000000000000000000000000000000..64a68a4d01a1da88f286af8fe8683d917ecb7b41
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.py
@@ -0,0 +1,34 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+
+from __future__ import (absolute_import, division, print_function)
+
+from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_model import CroppingModel
+from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_view import CroppingView
+from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_presenter import CroppingPresenter
+
+
+class CroppingWidget(object):
+    def __init__(self, parent, view=None):
+        if view is None:
+            self.view = CroppingView(parent)
+        else:
+            self.view = view
+        self.model = CroppingModel()
+        self.presenter = CroppingPresenter(self.model, self.view)
+
+    def is_valid(self):
+        return self.presenter.is_valid()
+
+    def get_custom_spectra(self):
+        return self.presenter.get_custom_spectra()
+
+    def get_bank(self):
+        return str(self.presenter.get_bank())
+
+    def is_custom(self):
+        return self.presenter.get_custom_spectra_enabled()
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.ui b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.ui
new file mode 100644
index 0000000000000000000000000000000000000000..452075824fbea9f6ac28a356008f6d019ae1f45e
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/cropping_widget.ui
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Form</class>
+ <widget class="QWidget" name="Form">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>516</width>
+    <height>86</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_bank">
+       <property name="text">
+        <string>Select Bank/Spectra:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1" colspan="2">
+      <widget class="QComboBox" name="combo_bank">
+       <item>
+        <property name="text">
+         <string>1 (North)</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>2 (South)</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Custom Spectra</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+     <item row="1" column="0" colspan="3">
+      <widget class="QWidget" name="widget_custom" native="true">
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <property name="leftMargin">
+         <number>0</number>
+        </property>
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <property name="rightMargin">
+         <number>0</number>
+        </property>
+        <property name="bottomMargin">
+         <number>0</number>
+        </property>
+        <item>
+         <widget class="QLabel" name="label_custom">
+          <property name="text">
+           <string>Custom Spectra:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLineEdit" name="edit_custom">
+          <property name="placeholderText">
+           <string/>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="label_customValid">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+            <horstretch>10</horstretch>
+            <verstretch>20</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="minimumSize">
+           <size>
+            <width>10</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="palette">
+           <palette>
+            <active>
+             <colorrole role="WindowText">
+              <brush brushstyle="SolidPattern">
+               <color alpha="255">
+                <red>170</red>
+                <green>0</green>
+                <blue>0</blue>
+               </color>
+              </brush>
+             </colorrole>
+            </active>
+            <inactive>
+             <colorrole role="WindowText">
+              <brush brushstyle="SolidPattern">
+               <color alpha="255">
+                <red>170</red>
+                <green>0</green>
+                <blue>0</blue>
+               </color>
+              </brush>
+             </colorrole>
+            </inactive>
+            <disabled>
+             <colorrole role="WindowText">
+              <brush brushstyle="SolidPattern">
+               <color alpha="255">
+                <red>118</red>
+                <green>116</green>
+                <blue>108</blue>
+               </color>
+              </brush>
+             </colorrole>
+            </disabled>
+           </palette>
+          </property>
+          <property name="text">
+           <string>*</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/__init__.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_model.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..c823a4f48519f8a885e9d3035334c951b4dc9a09
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_model.py
@@ -0,0 +1,69 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+
+from Engineering.gui.engineering_diffraction.tabs.common.cropping.cropping_model import CroppingModel
+
+
+class CroppingModelTest(unittest.TestCase):
+    def setUp(self):
+        self.model = CroppingModel()
+
+    def test_validate_single_valid_spectra(self):
+        self.assertTrue(self.model.validate_spectrum_numbers("1,2,3,4,5,6,7,8,9,10"))
+
+    def test_validate_single_valid_spectra_regular_whitespace(self):
+        self.assertTrue(self.model.validate_spectrum_numbers("1, 2, 3, 4, 5, 6, 7, 8, 9, 10"))
+
+    def test_validate_single_valid_spectra_irregular_whitespace(self):
+        self.assertTrue(self.model.validate_spectrum_numbers("1, 2,3,4,   5,6 ,7, 8,   9,  10"))
+
+    def test_validate_single_spectra_invalid_negative(self):
+        self.assertFalse(self.model.validate_spectrum_numbers("1,2,3,4,-5,6,7,8,9,10"))
+
+    def test_validate_single_spectra_invalid_spectrum(self):
+        self.assertFalse(self.model.validate_spectrum_numbers("1,2,3,4,5,6,77777,8,9,"))
+
+    def test_validate_ranged_spectra(self):
+        self.assertTrue(self.model.validate_spectrum_numbers("1-5, 5, 3  , 2-7, 7-13"))
+
+    def test_clean_spectrum_numbers_regular_whitespace(self):
+        self.assertEqual(self.model._clean_spectrum_numbers("1, 2, 5, 76, 3"), "1,2,5,76,3")
+
+    def test_clean_spectrum_numbers_irregular_whitespace(self):
+        self.assertEqual(self.model._clean_spectrum_numbers("1 , 2, 5      ,    76, 3     "),
+                         "1,2,5,76,3")
+
+    def test_clean_spectrum_numbers_regular_ranges(self):
+        self.assertEqual(self.model._clean_spectrum_numbers("1-2, 5-76, 3"), "1-2,5-76,3")
+
+    def test_clean_spectrum_numbers_reversed_ranges(self):
+        self.assertEqual(self.model._clean_spectrum_numbers("2-1, 76-5, 3"), "1-2,5-76,3")
+
+    def test_clean_spectrum_numbers_equal_range(self):
+        self.assertRaisesRegexp(ValueError,
+                                "Ranges cannot contain the same value twice. Invalid Range:*",
+                                self.model._clean_spectrum_numbers, "1-1, 76-76, 3")
+
+    def test_validate_and_clean_with_valid_input(self):
+        self.assertEqual(self.model.validate_and_clean_spectrum_numbers("1-6, 7-23, 46, 1"),
+                         ("", "1-6,7-23,46,1"))
+
+    def test_validate_and_clean_reverse_ranges(self):
+        self.assertEqual(self.model.validate_and_clean_spectrum_numbers("6-1, 7-24, 6-4,1"),
+                         ("", "1-6,7-24,4-6,1"))
+
+    def test_validate_and_clean_equal_ranges(self):
+        self.assertEqual(self.model.validate_and_clean_spectrum_numbers("6-6, 7-24, 6-4,1"),
+                         ("Ranges cannot contain the same value twice. Invalid Range: 6-6", ""))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_presenter.py b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_presenter.py
new file mode 100644
index 0000000000000000000000000000000000000000..84cb6f592b28af9a18f8a40a2c871aae90907f88
--- /dev/null
+++ b/scripts/Engineering/gui/engineering_diffraction/tabs/common/cropping/test/test_cropping_presenter.py
@@ -0,0 +1,66 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+
+from __future__ import (absolute_import, division, print_function)
+
+import unittest
+
+from mantid.py3compat import mock
+from Engineering.gui.engineering_diffraction.tabs.common.cropping import cropping_model, cropping_view, cropping_presenter
+
+dir_path = "Engineering.gui.engineering_diffraction.tabs.common.cropping"
+
+
+class CroppingPresenterTest(unittest.TestCase):
+    def setUp(self):
+        self.view = mock.create_autospec(cropping_view.CroppingView)
+        self.model = mock.create_autospec(cropping_model.CroppingModel)
+        self.presenter=  cropping_presenter.CroppingPresenter(self.model, self.view)
+
+    def test_combo_changed_index_bank_1(self):
+        self.presenter.on_combo_changed(0)
+
+        self.assertEqual(self.presenter.bank, 1)
+        self.assertFalse(self.presenter.custom_spectra_enabled)
+        self.view.set_custom_spectra_entry_hidden.assert_called_with()
+
+    def test_combo_changed_index_bank_2(self):
+        self.presenter.on_combo_changed(1)
+
+        self.assertEqual(self.presenter.bank, 2)
+        self.assertFalse(self.presenter.custom_spectra_enabled)
+        self.view.set_custom_spectra_entry_hidden.assert_called_with()
+
+    def test_combo_changed_index_custom(self):
+        self.model.validate_and_clean_spectrum_numbers.return_value = ("", "1400")
+        self.presenter.on_combo_changed(2)
+
+        self.assertEqual(self.presenter.bank, 0)
+        self.assertTrue(self.presenter.custom_spectra_enabled)
+        self.view.set_custom_spectra_entry_visible.assert_called_with()
+
+    def test_custom_spectra_changed_valid(self):
+        self.model.validate_and_clean_spectrum_numbers.return_value = ("", "1-5, 3-45")
+
+        self.presenter.on_spectra_changed("1-5, 45-3")
+
+        self.assertEqual(self.presenter.custom_spectra, "1-5, 3-45")
+        self.assertTrue(self.presenter.custom_valid)
+        self.view.set_invalid_indicator_hidden.assert_called_with()
+
+    def test_custom_spectra_changed_invalid(self):
+        self.model.validate_and_clean_spectrum_numbers.return_value = ("Error lol", "")
+
+        self.presenter.on_spectra_changed("1-5, 45-3")
+
+        self.assertEqual(self.presenter.custom_spectra, "")
+        self.assertFalse(self.presenter.custom_valid)
+        self.view.set_invalid_indicator_visible.assert_called_with("Error lol")
+
+
+if __name__ == '__main__':
+    unittest.main()