def load_data(self, prog_reporter): # Load the data and clean from Nans raw_xvals = self.getProperty('InputWorkspace').value.readX(0).copy() raw_yvals = self.getProperty('InputWorkspace').value.readY(0).copy() prog_reporter.report('Loaded data') # If the data does not have errors use poisson statistics create an workspace with added errors raw_error = self.getProperty('InputWorkspace').value.readE(0).copy() if len(np.argwhere(raw_error > 0)) == 0: raw_error = np.sqrt(raw_yvals) error_ws = CreateWorkspace(DataX=raw_xvals, DataY=raw_yvals, DataE=raw_error, StoreInADS=False) self.setPropertyValue( 'OutputWorkspace', '{}_with_errors'.format( self.getPropertyValue('InputWorkspace'))) self.setProperty('OutputWorkspace', error_ws) else: error_ws = self.getProperty('InputWorkspace').value self.setPropertyValue('OutputWorkspace', error_ws.getName()) return raw_xvals, raw_yvals, raw_error, error_ws
class FindPeaksAutomaticTest(unittest.TestCase): data_ws = None peak_guess_table = None peak_table_header = [ 'centre', 'error centre', 'height', 'error height', 'sigma', 'error sigma', 'area', 'error area' ] alg_instance = None x_values = None y_values = None def setUp(self): # Creating two peaks on an exponential background with gaussian noise self.x_values = np.linspace(0, 100, 1001) self.x_values = np.linspace(0, 100, 1001) self.centre = [25, 75] self.height = [35, 20] self.width = [10, 5] self.y_values = self.gaussian(self.x_values, self.centre[0], self.height[0], self.width[0]) self.y_values += self.gaussian(self.x_values, self.centre[1], self.height[1], self.width[1]) self.background = 10 * np.ones(len(self.x_values)) self.y_values += self.background # Generating a table with a guess of the position of the centre of the peaks peak_table = CreateEmptyTableWorkspace() peak_table.addColumn(type='float', name='Approximated Centre') peak_table.addRow([self.centre[0] + 2]) peak_table.addRow([self.centre[1] - 3]) self.peakids = [ np.argwhere(self.x_values == self.centre[0])[0, 0], np.argwhere(self.x_values == self.centre[1])[0, 0] ] # Generating a workspace with the data and a flat background self.raw_ws = CreateWorkspace(DataX=self.x_values, DataY=self.y_values, OutputWorkspace='raw_ws') self.data_ws = CreateWorkspace( DataX=np.concatenate((self.x_values, self.x_values)), DataY=np.concatenate((self.y_values, self.background)), DataE=np.sqrt(np.concatenate((self.y_values, self.background))), NSpec=2, OutputWorkspace='data_ws') self.peak_guess_table = peak_table self.alg_instance = _FindPeaksAutomatic.FindPeaksAutomatic() def tearDown(self): self.delete_if_present('data_ws') self.delete_if_present('peak_guess_table') self.delete_if_present('peak_table') self.delete_if_present('refit_peak_table') self.delete_if_present('fit_cost') self.delete_if_present('fit_result_NormalisedCovarianceMatrix') self.delete_if_present('fit_result_Parameters') self.delete_if_present('fit_result_Workspace') self.delete_if_present('fit_table') self.delete_if_present('data_table') self.delete_if_present('refit_data_table') self.delete_if_present('tmp_table') self.alg_instance = None self.peak_guess_table = None self.data_ws = None @staticmethod def gaussian(xvals, centre, height, sigma): exponent = (xvals - centre) / (np.sqrt(2) * sigma) return height * np.exp(-exponent * exponent) @staticmethod def delete_if_present(workspace): if workspace in mtd: DeleteWorkspace(workspace) def assertTableEqual(self, expected, actual): self.assertEqual(expected.columnCount(), actual.columnCount()) self.assertEqual(expected.rowCount(), actual.rowCount()) for i in range(expected.rowCount()): self.assertEqual(expected.row(i), actual.row(i)) def assertPeakFound(self, peak_params, centre, height, sigma, tolerance=0.01): if not np.isclose(peak_params['centre'], centre, rtol=tolerance): raise Exception( 'Expected {}, got {}. Difference greater than tolerance {}'. format(centre, peak_params['centre'], tolerance)) if not np.isclose(peak_params['height'], height, rtol=tolerance): raise Exception( 'Expected {}, got {}. Difference greater than tolerance {}'. format(height, peak_params['height'], tolerance)) if not np.isclose(peak_params['sigma'], sigma, rtol=tolerance): raise Exception( 'Expected {}, got {}. Difference greater than tolerance {}'. format(sigma, peak_params['sigma'], tolerance)) def test_algorithm_with_no_input_workspace_raises_exception(self): with self.assertRaises(RuntimeError): FindPeaksAutomatic() def test_algorithm_with_negative_acceptance_threshold_throws(self): with self.assertRaises(ValueError): FindPeaksAutomatic(InputWorkspace=self.data_ws, AcceptanceThreshold=-0.1, PlotPeaks=False) def test_algorithm_with_negative_smooth_window_throws(self): with self.assertRaises(ValueError): FindPeaksAutomatic(InputWorkspace=self.data_ws, SmoothWindow=-5, PlotPeaks=False) def test_algorithm_with_negative_num_bad_peaks_to_consider_throws(self): with self.assertRaises(ValueError): FindPeaksAutomatic(InputWorkspace=self.data_ws, BadPeaksToConsider=-3, PlotPeaks=False) def test_algorithm_with_negative_estimate_of_peak_sigma_throws(self): with self.assertRaises(ValueError): FindPeaksAutomatic(InputWorkspace=self.data_ws, EstimatePeakSigma=-3, PlotPeaks=False) def test_algorithm_with_negative_min_peak_sigma_throws(self): with self.assertRaises(ValueError): FindPeaksAutomatic(InputWorkspace=self.data_ws, MinPeakSigma=-0.1, PlotPeaks=False) def test_algorithm_with_negative_max_peak_sigma_throws(self): with self.assertRaises(ValueError): FindPeaksAutomatic(InputWorkspace=self.data_ws, MaxPeakSigma=-0.1, PlotPeaks=False) def test_algorithm_creates_all_output_workspaces(self): ws_name = self.raw_ws.getName() FindPeaksAutomatic(self.raw_ws) self.assertIn('{}_with_errors'.format(ws_name), mtd) self.assertIn('{}_{}'.format(self.raw_ws.getName(), 'properties'), mtd) self.assertIn( '{}_{}'.format(self.raw_ws.getName(), 'refit_properties'), mtd) def test_algorithm_does_not_create_temporary_workspaces(self): FindPeaksAutomatic(self.raw_ws) self.assertNotIn('ret', mtd) self.assertNotIn('raw_data_ws', mtd) self.assertNotIn('flat_ws', mtd) self.assertNotIn('fit_result_NormalisedCovarianceMatrix', mtd) self.assertNotIn('fit_result_Parameters', mtd) self.assertNotIn('fit_result_Workspace', mtd) self.assertNotIn('fit_cost', mtd) def test_output_tables_are_correctly_formatted(self): FindPeaksAutomatic(self.raw_ws, FitToBaseline=True) peak_table = mtd['{}_{}'.format(self.raw_ws.getName(), 'properties')] refit_peak_table = mtd['{}_{}'.format(self.raw_ws.getName(), 'refit_properties')] self.assertEqual(self.peak_table_header, peak_table.getColumnNames()) self.assertEqual(self.peak_table_header, refit_peak_table.getColumnNames()) self.assertEqual(2, peak_table.rowCount()) self.assertEqual(0, refit_peak_table.rowCount()) def test_single_erosion_returns_correct_result(self): yvals = np.array([-2, 3, 1, 0, 4]) self.assertEqual(-2, self.alg_instance._single_erosion(yvals, 2, 2)) def test_single_erosion_checks_extremes_of_list_correctly(self): yvals = np.array([-5, -3, 0, 1, -2, 2, 9]) self.assertEqual(-2, self.alg_instance._single_erosion(yvals, 3, 1)) self.assertEqual(-3, self.alg_instance._single_erosion(yvals, 3, 2)) def test_single_erosion_with_zero_window_does_nothing(self): yvals = np.array([-5, -3, 0, 1, -2, 2, 9]) self.assertEqual(0, self.alg_instance._single_erosion(yvals, 2, 0)) def test_single_dilation_returns_correct_result(self): yvals = np.array([-2, 3, 1, 0, 4]) self.assertEqual(4, self.alg_instance._single_dilation(yvals, 2, 2)) def test_single_dilation_checks_extremes_of_list_correctly(self): yvals = np.array([-5, 3, 0, -7, 2, -2, 9]) self.assertEqual(2, self.alg_instance._single_dilation(yvals, 3, 1)) self.assertEqual(3, self.alg_instance._single_dilation(yvals, 3, 2)) def test_single_dilation_with_zero_window_does_nothing(self): yvals = np.array([-5, -3, 0, 1, -2, 2, 9]) self.assertEqual(0, self.alg_instance._single_dilation(yvals, 2, 0)) def test_erosion_with_zero_window_is_an_invariant(self): np.testing.assert_equal(self.y_values, self.alg_instance.erosion(self.y_values, 0)) def test_erosion_calls_single_erosion_the_correct_number_of_times(self, ): with mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FindPeaksAutomatic._single_erosion' ) as mock_single_erosion: times = len(self.y_values) win_size = 2 call_list = [] for i in range(times): call_list.append(mock.call(self.y_values, i, win_size)) self.alg_instance.erosion(self.y_values, win_size) self.assertEqual(times, mock_single_erosion.call_count) mock_single_erosion.assert_has_calls(call_list, any_order=True) def test_dilation_with_zero_window_is_an_invariant(self): np.testing.assert_equal(self.y_values, self.alg_instance.dilation(self.y_values, 0)) def test_dilation_calls_single_erosion_the_correct_number_of_times(self): with mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FindPeaksAutomatic._single_dilation' ) as mock_single_dilation: times = len(self.y_values) win_size = 2 call_list = [] for i in range(times): call_list.append(mock.call(self.y_values, i, win_size)) self.alg_instance.dilation(self.y_values, win_size) self.assertEqual(times, mock_single_dilation.call_count) mock_single_dilation.assert_has_calls(call_list, any_order=True) @mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FindPeaksAutomatic.erosion' ) @mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FindPeaksAutomatic.dilation' ) def test_opening_calls_correct_functions_in_correct_order( self, mock_dilation, mock_erosion): win_size = 3 self.alg_instance.opening(self.y_values, win_size) self.assertEqual(mock_erosion.call_count, 1) self.assertEqual(mock_dilation.call_count, 1) erosion_ret = self.alg_instance.erosion(self.y_values, win_size) mock_erosion.assert_called_with(self.y_values, win_size) mock_dilation.assert_called_with(erosion_ret, win_size) @mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FindPeaksAutomatic.opening' ) @mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FindPeaksAutomatic.dilation' ) @mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FindPeaksAutomatic.erosion' ) def test_average_calls_right_functions_in_right_order( self, mock_erosion, mock_dilation, mock_opening): win_size = 3 self.alg_instance.average(self.y_values, win_size) self.assertEqual(mock_erosion.call_count, 1) self.assertEqual(mock_dilation.call_count, 1) self.assertEqual(mock_opening.call_count, 2) op_ret = self.alg_instance.opening(self.y_values, win_size) mock_opening.assert_called_with(self.y_values, win_size) mock_dilation.assert_called_with(op_ret, win_size) mock_erosion.assert_called_with(op_ret, win_size) def test_generate_peak_guess_table_correctly_formats_table(self): peakids = [2, 4, 10, 34] peak_guess_table = self.alg_instance.generate_peak_guess_table( self.x_values, peakids) self.assertEqual(peak_guess_table.getColumnNames(), ['centre']) def test_generate_peak_guess_table_with_no_peaks_generates_empty_table( self): peak_guess_table = self.alg_instance.generate_peak_guess_table( self.x_values, []) self.assertEqual(peak_guess_table.rowCount(), 0) def test_generate_peak_guess_table_adds_correct_values_of_peak_centre( self): peakids = [2, 23, 19, 34, 25, 149, 234] peak_guess_table = self.alg_instance.generate_peak_guess_table( self.x_values, peakids) for i, pid in enumerate(sorted(peakids)): self.assertAlmostEqual( peak_guess_table.row(i)['centre'], self.x_values[pid], 5) def test_find_good_peaks_calls_fit_gaussian_peaks_twice_if_no_peaks_given( self): with mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FitGaussianPeaks' ) as mock_fit: tmp_table = CreateEmptyTableWorkspace() tmp_table.addColumn(type='float', name='chi2') tmp_table.addColumn(type='float', name='poisson') tmp_table.addRow([10, 20]) mock_fit.return_value = (mock.MagicMock(), mock.MagicMock(), tmp_table) self.alg_instance.min_sigma = 1 self.alg_instance.max_sigma = 10 self.alg_instance.find_good_peaks(self.x_values, [], 0.1, 5, False, self.data_ws, 5) self.assertEqual(2, mock_fit.call_count) def _table_side_effect(self, idx): raise ValueError('Index = %d' % idx) def test_find_good_peaks_selects_correct_column_for_error(self): with mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.FitGaussianPeaks' ) as mock_fit: mock_table = mock.Mock() mock_table.column.side_effect = self._table_side_effect mock_fit.return_value = None, None, mock_table # chi2 cost with self.assertRaises(ValueError) as chi2: self.alg_instance.find_good_peaks(self.x_values, [], 0.1, 5, False, self.data_ws, 5) # poisson cost with self.assertRaises(ValueError) as poisson: self.alg_instance.find_good_peaks(self.x_values, [], 0.1, 5, True, self.data_ws, 5) self.assertIn('Index = 0', chi2.exception.args) self.assertNotIn('Index = 1', chi2.exception.args) self.assertNotIn('Index = 0', poisson.exception.args) self.assertIn('Index = 1', poisson.exception.args) def test_find_good_peaks_returns_correct_peaks(self): self.alg_instance._min_sigma = 1 self.alg_instance._max_sigma = 10 actual_peaks, peak_table, refit_peak_table = self.alg_instance.find_good_peaks( self.x_values, self.peakids, 0, 5, False, self.data_ws, 5) peak1 = peak_table.row(0) peak2 = peak_table.row(1) self.assertEquals(self.peakids, actual_peaks) self.assertEqual(0, refit_peak_table.rowCount()) self.assertEqual(refit_peak_table.getColumnNames(), peak_table.getColumnNames()) self.assertPeakFound(peak1, self.centre[0], self.height[0] + 10, self.width[0], 0.05) self.assertPeakFound(peak2, self.centre[1], self.height[1] + 10, self.width[1], 0.05) def test_find_peaks_is_called_if_scipy_version_higher_1_1_0(self): mock_scipy = mock.MagicMock() mock_scipy.__version__ = '1.1.0' mock_scipy.signal.find_peaks.return_value = (self.peakids, { 'prominences': self.peakids }) with mock.patch.dict('sys.modules', scipy=mock_scipy): self.alg_instance.process(self.x_values, self.y_values, raw_error=np.sqrt(self.y_values), acceptance=0, average_window=50, bad_peak_to_consider=2, use_poisson=False, peak_width_estimate=5, fit_to_baseline=False, prog_reporter=mock.Mock()) self.assertEqual(2, mock_scipy.signal.find_peaks.call_count) self.assertEqual(0, mock_scipy.signal.find_peaks_cwt.call_count) def test_find_peaks_cwt_is_called_if_scipy_version_lower_1_1_0(self): mock_scipy = mock.MagicMock() mock_scipy.__version__ = '1.0.0' mock_scipy.signal.find_peaks.return_value = (self.peakids, { 'prominences': self.peakids }) with mock.patch.dict('sys.modules', scipy=mock_scipy): self.alg_instance.process(self.x_values, self.y_values, raw_error=np.sqrt(self.y_values), acceptance=0, average_window=50, bad_peak_to_consider=2, use_poisson=False, peak_width_estimate=5, fit_to_baseline=False, prog_reporter=mock.Mock()) self.assertEqual(0, mock_scipy.signal.find_peaks.call_count) self.assertEqual(1, mock_scipy.signal.find_peaks_cwt.call_count) def test_process_calls_find_good_peaks(self): with mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.CreateWorkspace' ) as mock_create_ws: mock_create_ws.return_value = self.data_ws self.alg_instance.find_good_peaks = mock.Mock() self.alg_instance.process(self.x_values, self.y_values, raw_error=np.sqrt(self.y_values), acceptance=0, average_window=50, bad_peak_to_consider=2, use_poisson=False, peak_width_estimate=5, fit_to_baseline=False, prog_reporter=mock.Mock()) base = self.alg_instance.average(self.y_values, 50) base += self.alg_instance.average(self.y_values - base, 50) flat = self.y_values - base self.assertEqual(1, self.alg_instance.find_good_peaks.call_count) self.alg_instance.find_good_peaks.asser_called_with( self.x_values, flat, acceptance=0, bad_peak_to_consider=2, use_poisson=False, fit_ws=self.data_ws, peak_width_estimate=5) def test_process_returns_the_return_value_of_find_good_peaks(self): with mock.patch( 'plugins.algorithms.WorkflowAlgorithms.FindPeaksAutomatic.CreateWorkspace' ) as mock_create_ws: mock_create_ws.return_value = self.data_ws win_size = 500 actual_return = self.alg_instance.process( self.x_values, self.y_values, raw_error=np.sqrt(self.y_values), acceptance=0, average_window=win_size, bad_peak_to_consider=2, use_poisson=False, peak_width_estimate=5, fit_to_baseline=False, prog_reporter=mock.Mock()) import copy actual_return = copy.deepcopy(actual_return) base = self.alg_instance.average(self.y_values, win_size) base += self.alg_instance.average(self.y_values - base, win_size) expected_return = self.alg_instance.find_good_peaks( self.x_values, self.peakids, acceptance=0, bad_peak_to_consider=2, use_poisson=False, fit_ws=self.data_ws, peak_width_estimate=5), base self.assertEqual(expected_return[0][0], actual_return[0][0]) self.assertTableEqual(expected_return[0][1], actual_return[0][1]) np.testing.assert_almost_equal(expected_return[1], actual_return[1]) def _assert_matplotlib_not_present(self, *args): import sys self.assertNotIn('matplotlib.pyplot', sys.modules) # If matplotlib.pyplot is imported other tests fail on windows and ubuntu def test_matplotlib_pyplot_is_not_imported(self): self.alg_instance.dilation = mock.Mock( side_effect=self._assert_matplotlib_not_present) self.alg_instance.opening(self.y_values, 0) def test_that_algorithm_finds_peaks_correctly(self): FindPeaksAutomatic( InputWorkspace=self.raw_ws, SmoothWindow=500, EstimatePeakSigma=5, MinPeakSigma=3, MaxPeakSigma=15, ) peak_table = mtd['{}_{}'.format(self.raw_ws.getName(), 'properties')] refit_peak_table = mtd['{}_{}'.format(self.raw_ws.getName(), 'refit_properties')] self.assertEqual(2, peak_table.rowCount()) self.assertEqual(0, refit_peak_table.rowCount()) self.assertPeakFound(peak_table.row(0), self.centre[0], self.height[0], self.width[0], 0.05) self.assertPeakFound(peak_table.row(1), self.centre[1], self.height[1], self.width[1], 0.05)