def test_automatic_background_subtraction_with_roi(self):
        x = np.linspace(0, 24, 2500)
        y = np.zeros(x.shape)

        peaks = [
            [10, 3, 0.1],
            [12, 4, 0.1],
            [12, 6, 0.1],
        ]
        for peak in peaks:
            y += gaussian(x, peak[0], peak[1], peak[2])
        y_bkg = x * 0.4 + 5.0
        y_measurement = y + y_bkg

        roi = [1, 23]

        spectrum = Spectrum(x, y_measurement)

        auto_background_subtraction_parameters = [2, 50, 50]
        spectrum.set_auto_background_subtraction(auto_background_subtraction_parameters, roi)

        x_spec, y_spec = spectrum.data

        self.assertGreater(x_spec[0],roi[0])
        self.assertLess(x_spec[-1], roi[1])
    def test_loading_chi_file(self):
        spec = Spectrum()
        x, y = spec.data

        spec.load(os.path.join(data_path,'spectrum_001.chi'))
        new_x, new_y = spec.data

        self.assertNotEqual(len(x), len(new_x))
        self.assertNotEqual(len(y), len(new_y))
    def test_setting_new_data(self):
        spec = Spectrum()
        x = np.linspace(0, 10)
        y = np.sin(x)
        spec.data = x, y

        new_x, new_y = spec.data
        self.array_almost_equal(new_x, x)
        self.array_almost_equal(new_y, y)
    def test_background_out_of_range_throws_error(self):
        x1 = np.linspace(0, 10)
        x2 = np.linspace(-10, -1)

        spec = Spectrum(x1, x1)
        background_spectrum = Spectrum(x2, x2)

        with self.assertRaises(BkgNotInRangeError):
            spec.background_spectrum = background_spectrum
    def test_using_background_spectrum(self):
        x = np.linspace(-5, 5, 100)
        spec_y = x ** 2
        bkg_y = x

        spec = Spectrum(x, spec_y)
        background_spectrum = Spectrum(x, bkg_y)

        spec.background_spectrum = background_spectrum
        new_x, new_y = spec.data

        self.array_almost_equal(new_x, x)
        self.array_almost_equal(new_y, spec_y - bkg_y)
    def test_using_background_spectrum_with_different_spacing(self):
        x = np.linspace(-5, 5, 100)
        spec_y = x ** 2
        x_bkg = np.linspace(-5, 5, 99)
        bkg_y = x_bkg

        spec = Spectrum(x, spec_y)
        background_spectrum = Spectrum(x_bkg, bkg_y)

        spec.background_spectrum = background_spectrum
        new_x, new_y = spec.data

        self.array_almost_equal(new_x, x)
        self.array_almost_equal(new_y, spec_y - x)
    def __init__(self):
        super(SpectrumModel, self).__init__()
        self.spectrum = Spectrum()
        self.overlays = []
        self.phases = []

        self.file_iteration_mode = 'number'
        self.file_name_iterator = FileNameIterator()

        self.bkg_ind = -1
        self.spectrum_filename = ''
    def test_automatic_background_subtraction(self):
        x = np.linspace(0, 24, 2500)
        y = np.zeros(x.shape)

        peaks = [
            [10, 3, 0.1],
            [12, 4, 0.1],
            [12, 6, 0.1],
        ]
        for peak in peaks:
            y += gaussian(x, peak[0], peak[1], peak[2])
        y_bkg = x * 0.4 + 5.0
        y_measurement = y + y_bkg

        spectrum = Spectrum(x, y_measurement)

        auto_background_subtraction_parameters = [2, 50, 50]
        spectrum.set_auto_background_subtraction(auto_background_subtraction_parameters)

        x_spec, y_spec = spectrum.data

        self.array_almost_equal(y_spec, y)
    def test_saving_a_file(self):
        x = np.linspace(-5, 5, 100)
        y = x ** 2
        spec = Spectrum(x, y)
        filename = os.path.join(data_path, "test.dat")
        spec.save(filename)

        spec2 = Spectrum()
        spec2.load(filename)

        spec2_x, spec2_y = spec2.data
        self.array_almost_equal(spec2_x, x)
        self.array_almost_equal(spec2_y, y)

        os.remove(filename)
 def test_loading_invalid_file(self):
     spec = Spectrum()
     self.assertEqual(-1, spec.load(os.path.join(data_path,'wrong_file_format.txt')))
class SpectrumModel(QtCore.QObject):
    """
    Main Spectrum handling class. Supporting several features:
      - loading spectra from any tabular source (readable by numpy)
      - having overlays
      - setting overlays as background
      - spectra and overlays can be scaled and have offset values

    all changes to the internal data throw pyqtSignals.
    """
    spectrum_changed = QtCore.pyqtSignal()
    overlay_changed = QtCore.pyqtSignal(int)  # changed index
    overlay_added = QtCore.pyqtSignal()
    overlay_removed = QtCore.pyqtSignal(int)  # removed index
    overlay_set_as_bkg = QtCore.pyqtSignal(int)  # index set as background
    overlay_unset_as_bkg = QtCore.pyqtSignal(int)  # index unset os background

    def __init__(self):
        super(SpectrumModel, self).__init__()
        self.spectrum = Spectrum()
        self.overlays = []
        self.phases = []

        self.file_iteration_mode = 'number'
        self.file_name_iterator = FileNameIterator()

        self.bkg_ind = -1
        self.spectrum_filename = ''

    def set_spectrum(self, x, y, filename='', unit=''):
        """
        set the current data spectrum.
        :param x: x-values
        :param y: y-values
        :param filename: name for the spectrum, defaults to ''
        :param unit: unit for the x values
        """
        self.spectrum_filename = filename
        self.spectrum.data = (x, y)
        self.spectrum.name = get_base_name(filename)
        self.unit = unit
        self.spectrum_changed.emit()

    def load_spectrum(self, filename):
        """
        Loads a spectrum from a tabular spectrum file (2 column txt file)
        :param filename: filename of the data file
        """
        logger.info("Load spectrum: {0}".format(filename))
        self.spectrum_filename = filename

        skiprows = 0
        if filename.endswith('.chi'):
            skiprows = 4
        self.spectrum.load(filename, skiprows)
        self.file_name_iterator.update_filename(filename)
        self.spectrum_changed.emit()

    def save_spectrum(self, filename, header=None, subtract_background=False):
        """
        Saves the current data spectrum.
        :param filename: where to save
        :param header: you can specify any specific header
        :param subtract_background: whether or not the background set will be used for saving or not
        """
        if subtract_background:
            x, y = self.spectrum.data
        else:
            x, y = self.spectrum._original_x, self.spectrum._original_y

        file_handle = open(filename, 'w')
        num_points = len(x)

        if filename.endswith('.chi'):
            if header is None or header == '':
                file_handle.write(filename + '\n')
                file_handle.write(self.unit + '\n\n')
                file_handle.write("       {0}\n".format(num_points))
            else:
                file_handle.write(header)
            for ind in xrange(num_points):
                file_handle.write(' {0:.7E}  {1:.7E}\n'.format(x[ind], y[ind]))
        else:
            if header is not None:
                file_handle.write(header)
                file_handle.write('\n')
            for ind in xrange(num_points):
                file_handle.write('{0:.9E}  {1:.9E}\n'.format(x[ind], y[ind]))
        file_handle.close()

    def get_spectrum(self):
        return self.spectrum

    def load_next_file(self, step=1):
        """
        Loads the next file from a sequel of filenames (e.g. *_001.xy --> *_002.xy)
        It assumes that the file numbers are at the end of the filename
        """
        next_file_name = self.file_name_iterator.get_next_filename(mode=self.file_iteration_mode, step=step)
        if next_file_name is not None:
            self.load_spectrum(next_file_name)
            return True
        return False

    def load_previous_file(self, step=1):
        """
        Loads the previous file from a sequel of filenames (e.g. *_002.xy --> *_001.xy)
        It assumes that the file numbers are at the end of the filename
        """
        next_file_name = self.file_name_iterator.get_previous_filename(mode=self.file_iteration_mode, step=step)
        if next_file_name is not None:
            self.load_spectrum(next_file_name)
            return True
        return False

    def set_file_iteration_mode(self, mode):
        if mode == 'number':
            self.file_iteration_mode = 'number'
            self.file_name_iterator.create_timed_file_list = False
        elif mode == 'time':
            self.file_iteration_mode = 'time'
            self.file_name_iterator.create_timed_file_list = True
            self.file_name_iterator.update_filename(self.filename)

    def add_overlay(self, x, y, name=''):
        """
        Adds an overlay to the list of overlays
        :param x: x-values
        :param y: y-values
        :param name: name of overlay to be used for displaying etc.
        """
        self.overlays.append(Spectrum(x, y, name))
        self.overlay_added.emit()

    def remove_overlay(self, ind):
        """
        Removes an overlay from the list of overlays
        :param ind: index of the overlay
        """
        if ind >= 0:
            del self.overlays[ind]
            if self.bkg_ind > ind:
                self.bkg_ind -= 1
            elif self.bkg_ind == ind:
                self.spectrum.unset_background_spectrum()
                self.bkg_ind = -1
                self.spectrum_changed.emit()
            self.overlay_removed.emit(ind)

    def get_overlay(self, ind):
        """
        :param ind: overlay ind
        :return: returns overlay if existent or None if it does not exist
        :type return: Spectrum
        """
        try:
            return self.overlays[ind]
        except IndexError:
            return None


    def add_spectrum_as_overlay(self):
        """
        Adds the current data spectrum as overlay to the list of overlays
        """
        current_spectrum = deepcopy(self.spectrum)
        overlay_spectrum = Spectrum(current_spectrum.x,
                                    current_spectrum.y,
                                    current_spectrum.name)
        self.overlays.append(overlay_spectrum)
        self.overlay_added.emit()

    def add_overlay_file(self, filename):
        """
        Reads a 2-column (x,y) text file and adds it as overlay to the list of overlays
        :param filename: path of the file to be loaded
        """
        self.overlays.append(Spectrum())
        self.overlays[-1].load(filename)
        self.overlay_added.emit()

    def get_overlay_name(self, ind):
        """
        :param ind: overlay index
        """
        return self.overlays[-1].name

    def set_overlay_scaling(self, ind, scaling):
        """
        Sets the scaling of the specified overlay
        :param ind: index of the overlay
        :param scaling: new scaling value
        """
        self.overlays[ind].scaling = scaling
        self.overlay_changed.emit(ind)
        if self.bkg_ind == ind:
            self.spectrum_changed.emit()

    def get_overlay_scaling(self, ind):
        """
        Returns the scaling of the specified overlay
        :param ind: index of the overlay
        :return: scaling value
        """
        return self.overlays[ind].scaling

    def set_overlay_offset(self, ind, offset):
        """
        Sets the offset of the specified overlay
        :param ind: index of the overlay
        :param offset: new offset value
        """
        self.overlays[ind].offset = offset
        self.overlay_changed.emit(ind)
        if self.bkg_ind == ind:
            self.spectrum_changed.emit()

    def get_overlay_offset(self, ind):
        """
        Return the offset of the specified overlay
        :param ind: index of the overlay
        :return: overlay value
        """
        return self.overlays[ind].offset

    def set_overlay_as_bkg(self, ind):
        """
        Sets an overlay as background for the data spectrum, and unsets any previously used background
        :param ind: index of the overlay
        """
        if self.bkg_ind >= 0:
            self.unset_overlay_as_bkg()
        self.bkg_ind = ind
        self.spectrum.background_spectrum = self.overlays[ind]
        self.spectrum_changed.emit()
        self.overlay_set_as_bkg.emit(ind)

    def set_spectrum_as_bkg(self):
        """
        Adds the current spectrum as Overlay and sets it as background spectrum and unsets any previously used
        background.
        """
        self.add_spectrum_as_overlay()
        self.set_overlay_as_bkg(len(self.overlays) - 1)

    def unset_overlay_as_bkg(self):
        """
        Unsets the currently used background overlay.
        """
        previous_bkg_ind = self.bkg_ind
        self.bkg_ind = -1
        self.spectrum.unset_background_spectrum()
        self.spectrum_changed.emit()
        self.overlay_unset_as_bkg.emit(previous_bkg_ind)

    def overlay_is_bkg(self, ind):
        """
        :param ind: overlay ind
        """
        return ind == self.bkg_ind and self.bkg_ind != -1

    def set_auto_background_subtraction(self, parameters, roi=None):
        """
        Enables auto background extraction and removal from the data spectrum
        :param parameters: array of parameters with [window_width, iterations, polynomial_order]
        :param roi: array of size two with [xmin, xmax] specifying the range for which the background subtraction
        will be performed
        """
        self.spectrum.set_auto_background_subtraction(parameters, roi)
        self.spectrum_changed.emit()

    def unset_auto_background_subtraction(self):
        """
        Disables auto background extraction and removal.
        """
        self.spectrum.unset_auto_background_subtraction()
        self.spectrum_changed.emit()