diff --git a/docs/base_computation.rst b/docs/base_computation.rst index 4afce8c4..f129054e 100644 --- a/docs/base_computation.rst +++ b/docs/base_computation.rst @@ -77,9 +77,9 @@ the error for the computation in the dialog shown below. :align: center The error report shows the difference between the measured fiducial points and points estimated using the calculated -base matrix, and the average norm of the error. The error report could be used to evaluate which points are contributing -most of error so they could be removed or remeasured, it could reveal situations when the sample is moving (i.e. not -rigidly mounted), or it could reveal kinematic errors in the robot. +base matrix, and the distance (euclidean norm) between both sets of points. The error report could be used to evaluate +which points are contributing most of error so they could be removed or remeasured, it could reveal situations when the +sample is moving (i.e. not rigidly mounted), or it could reveal kinematic errors in the robot. Clicking the Accept button in the error report will apply the new base matrix to the positioning system and the change in position and orientation can be seen in the graphic window. Clicking the Cancel button will discard the computed diff --git a/docs/images/alignment_report.png b/docs/images/alignment_report.png index 7f32a10c..a4e9e951 100644 Binary files a/docs/images/alignment_report.png and b/docs/images/alignment_report.png differ diff --git a/docs/images/bad_alignment_report.png b/docs/images/bad_alignment_report.png index df3450f4..ab9a6495 100644 Binary files a/docs/images/bad_alignment_report.png and b/docs/images/bad_alignment_report.png differ diff --git a/docs/images/base_computation_example_4.png b/docs/images/base_computation_example_4.png index b6f5f7fe..1f882a9b 100644 Binary files a/docs/images/base_computation_example_4.png and b/docs/images/base_computation_example_4.png differ diff --git a/docs/images/detailed_alignment_report.png b/docs/images/detailed_alignment_report.png index 52a05dc6..3e894db2 100644 Binary files a/docs/images/detailed_alignment_report.png and b/docs/images/detailed_alignment_report.png differ diff --git a/docs/images/fixed_alignment_report.png b/docs/images/fixed_alignment_report.png index b2e9f4c1..0bea8b8e 100644 Binary files a/docs/images/fixed_alignment_report.png and b/docs/images/fixed_alignment_report.png differ diff --git a/docs/images/order_alignment_report.png b/docs/images/order_alignment_report.png index 8cdc9df6..3e5cbe18 100644 Binary files a/docs/images/order_alignment_report.png and b/docs/images/order_alignment_report.png differ diff --git a/docs/images/path_length_plotter.png b/docs/images/path_length_plotter.png index 5c52b96e..08811288 100644 Binary files a/docs/images/path_length_plotter.png and b/docs/images/path_length_plotter.png differ diff --git a/docs/images/run_sim.png b/docs/images/run_sim.png index ba22c077..74c73410 100644 Binary files a/docs/images/run_sim.png and b/docs/images/run_sim.png differ diff --git a/sscanss/app/dialogs/misc.py b/sscanss/app/dialogs/misc.py index a44c587f..698ce643 100644 --- a/sscanss/app/dialogs/misc.py +++ b/sscanss/app/dialogs/misc.py @@ -5,7 +5,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure -from sscanss.config import path_for, __version__ +from sscanss.config import path_for, __version__, settings from sscanss.core.instrument import IKSolver from sscanss.core.util import (DockFlag, Attributes, Accordion, Pane, create_tool_button, Banner, compact_path, StyledTabWidget) @@ -321,7 +321,7 @@ def __init__(self, parent, indices, enabled, points, transform_result, end_confi self.banner.hide() self.main_layout.addSpacing(5) - self.result_text = '
The Average (RMS) Error is ' \ + self.result_text = '
The Average Distance Error is ' \ '{:.3f} {}
' self.result_label = QtWidgets.QLabel() self.result_label.setTextFormat(QtCore.Qt.RichText) @@ -520,13 +520,14 @@ def __init__(self, parent, pose_id, fiducial_id, error): self.error_table = QtWidgets.QTableWidget() self.error_table.setColumnCount(6) - self.error_table.setHorizontalHeaderLabels(['Pose ID', 'Fiducial ID', '\u0394X', '\u0394Y', '\u0394Z', 'Norm']) + self.error_table.setHorizontalHeaderLabels(['Pose ID', 'Fiducial ID', '\u0394X', '\u0394Y', '\u0394Z', + 'Euclidean\n Norm']) self.error_table.horizontalHeaderItem(0).setToolTip('Index of pose') self.error_table.horizontalHeaderItem(1).setToolTip('Index of fiducial point') self.error_table.horizontalHeaderItem(2).setToolTip('Difference between computed and measured points (X)') self.error_table.horizontalHeaderItem(3).setToolTip('Difference between computed and measured points (Y)') self.error_table.horizontalHeaderItem(4).setToolTip('Difference between computed and measured points (Z)') - self.error_table.horizontalHeaderItem(5).setToolTip('Magnitude of point differences') + self.error_table.horizontalHeaderItem(5).setToolTip('Euclidean distance between and measured points') self.error_table.setAlternatingRowColors(True) self.error_table.setMinimumHeight(600) @@ -569,7 +570,7 @@ def updateTable(self, pose_id, fiducial_id, error): """ tol = 0.1 norm = np.linalg.norm(error, axis=1) - result_text = 'The Average (RMS) Error is ' \ + result_text = '
The Average Distance Error is ' \ '{:.3f} mm
' mean = np.mean(norm) colour = 'Tomato' if mean > tol else 'SeaGreen' @@ -761,8 +762,7 @@ def setup(self, no_render=False): self.progress_bar.setMaximum(self.simulation.count) self.updateProgress(SimulationDialog.State.Starting) self.result_list.clear() - self.result_counts = {key: 0 for key in self.ResultKey} - self.filter_button_group.button(self.ResultKey.Skip.value).setVisible(False) + self.resetFilterButtons() self.simulation.result_updated.connect(self.showResult) self.simulation.stopped.connect(lambda: self.updateProgress(SimulationDialog.State.Stopped)) @@ -847,6 +847,13 @@ def getResultKey(self, result): return key + def resetFilterButtons(self): + self.result_counts = {key: 0 for key in self.ResultKey} + for key in self.ResultKey: + self.result_counts[key] = 0 + self.filter_button_group.button(key.value).setText('0') + self.filter_button_group.button(self.ResultKey.Skip.value).setVisible(False) + def __updateFilterButton(self, result_key): """Updates the filter button for the given key @@ -1225,7 +1232,7 @@ def __init__(self, parent): tool_layout.setAlignment(QtCore.Qt.AlignBottom) self.main_layout.addLayout(tool_layout) - layout = QtWidgets.QVBoxLayout() + layout = QtWidgets.QHBoxLayout() layout.setSpacing(1) self.alignment_combobox = QtWidgets.QComboBox() self.alignment_combobox.setView(QtWidgets.QListView()) @@ -1233,29 +1240,29 @@ def __init__(self, parent): self.alignment_combobox.setMinimumWidth(100) self.alignment_combobox.activated.connect(self.plot) if self.simulation.shape[2] > 1: - layout.addWidget(QtWidgets.QLabel('Alignment')) + layout.addWidget(QtWidgets.QLabel('Alignment: ')) layout.addWidget(self.alignment_combobox) tool_layout.addLayout(layout) - layout = QtWidgets.QVBoxLayout() + layout = QtWidgets.QHBoxLayout() layout.setSpacing(1) self.detector_combobox = QtWidgets.QComboBox() self.detector_combobox.setView(QtWidgets.QListView()) self.detector_combobox.addItems(['Both', *self.simulation.detector_names]) - self.detector_combobox.setMinimumWidth(50) + self.detector_combobox.setMinimumWidth(100) self.detector_combobox.activated.connect(self.plot) if self.simulation.shape[1] > 1: - layout.addWidget(QtWidgets.QLabel('Detector')) + layout.addWidget(QtWidgets.QLabel('Detector: ')) layout.addWidget(self.detector_combobox) tool_layout.addLayout(layout) - layout = QtWidgets.QVBoxLayout() - layout.setSpacing(1) + # layout = QtWidgets.QVBoxLayout() + # layout.setSpacing(1) self.grid_checkbox = QtWidgets.QCheckBox('Show Grid') self.grid_checkbox.clicked.connect(self.plot) - layout.addSpacing(15) - layout.addWidget(self.grid_checkbox) - tool_layout.addLayout(layout) + # layout.addSpacing(15) + tool_layout.addWidget(self.grid_checkbox) + # tool_layout.addLayout(layout) tool_layout.addStretch(1) self.export_button = QtWidgets.QPushButton('Export') @@ -1288,17 +1295,14 @@ def export(self): if os.path.splitext(filename)[1].lower() == '.txt': header = 'Index' data = [self.axes.lines[0].get_xdata()] + fmt = ['%d'] for line in self.axes.lines: data.append(line.get_ydata()) + fmt.append('%.3f') header = f'{header}\t{line.get_label()}' - alignment = self.alignment_combobox.currentIndex() - if alignment == 0: - header = f'Path lengths for each measurement\n{header}' - else: - header = f'Path lengths for each measurement in vector alignment {alignment}\n{header}' - np.savetxt(filename, np.column_stack(data), fmt=['%d', '%.3f', '%.3f'], delimiter='\t', - header=header, comments='') + header = f'{self.axes.get_title()}\n{header}' + np.savetxt(filename, np.column_stack(data), fmt=fmt, delimiter='\t', header=header, comments='') else: self.figure.savefig(filename, dpi=300, bbox_inches='tight') except OSError as e: @@ -1314,26 +1318,32 @@ def plot(self): names = self.simulation.detector_names path_length = self.simulation.path_lengths + colours = [None] * len(names) + colours[:2] = [settings.value(settings.Key.Vector_1_Colour), settings.value(settings.Key.Vector_2_Colour)] if detector_index > -1: names = names[detector_index:detector_index+1] path_length = path_length[:, detector_index:detector_index+1, :] + colours = colours[detector_index:detector_index+1] if alignment_index > -1: path_length = path_length[:, :, alignment_index] + title = f'Path lengths for measurements in alignment {alignment_index + 1}' else: if self.simulation.args['align_first_order']: path_length = np.column_stack(np.dsplit(path_length, path_length.shape[2])).reshape(-1, len(names)) else: path_length = np.row_stack(np.dsplit(path_length, path_length.shape[2])).reshape(-1, len(names)) + title = 'Path lengths for measurements' label = np.arange(1, path_length.shape[0] + 1) step = 1 if path_length.shape[0] < 10 else path_length.shape[0] // 10 self.axes.set_xticks(label[::step]) for i in range(path_length.shape[1]): - self.axes.plot(label, path_length[:, i], 'o-', label=names[i]) + self.axes.plot(label, path_length[:, i], 'o-', label=names[i], color=colours[i]) self.axes.legend() + self.axes.set_title(title) self.axes.set_xlabel('Measurement Index', labelpad=10) self.axes.set_ylabel('Path Length (mm)') self.axes.minorticks_on() diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 2c693bd6..56ccb53b 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -1238,6 +1238,7 @@ def testExport(self, savetxt): expected = [[1, 1, 2], [2, 0, 0], [3, 3, 4], [4, 0, 0], [5, 5, 6], [6, 0, 0]] self.assertEqual(savetxt.call_count, 2) np.testing.assert_array_almost_equal(savetxt.call_args[0][1], expected, decimal=5) + self.assertEqual(len(savetxt.call_args[1]['fmt']), 3) combo = self.dialog.alignment_combobox combo.setCurrentIndex(2) @@ -1245,6 +1246,7 @@ def testExport(self, savetxt): self.dialog.export() self.assertEqual(savetxt.call_count, 3) np.testing.assert_array_almost_equal(savetxt.call_args[0][1], [[1, 0, 0], [2, 0, 0], [3, 0, 0]], decimal=5) + self.assertEqual(len(savetxt.call_args[1]['fmt']), 3) combo.setCurrentIndex(1) combo = self.dialog.detector_combobox @@ -1253,6 +1255,7 @@ def testExport(self, savetxt): self.dialog.export() self.assertEqual(savetxt.call_count, 4) np.testing.assert_array_almost_equal(savetxt.call_args[0][1], [[1, 2], [2, 4], [3, 6]], decimal=5) + self.assertEqual(len(savetxt.call_args[1]['fmt']), 2) self.presenter.notifyError.assert_not_called() savetxt.side_effect = OSError