def test_tool_filter(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() widget = view.addLaser(Laser(linear_data(["a", "b", "c"]))) tool = DriftTool(view.activeWidget()) view.addTab("Tool", tool) qtbot.waitExposed(tool) tool.combo_element.setCurrentText("a") tool.combo_element.activated.emit(0) tool.spinbox_degree.setValue(1) tool.apply() assert np.all(np.isclose(widget.laser.data["a"], widget.laser.data["a"][0][0])) tool.combo_element.setCurrentText("b") tool.combo_element.activated.emit(0) assert not np.all(np.isclose(widget.laser.data["b"], widget.laser.data["c"][0][0])) tool.check_apply_all.setChecked(True) tool.apply() assert np.all(np.isclose(widget.laser.data["b"], widget.laser.data["c"][0][0]))
def test_export_dialog_names(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) view = viewspace.activeView() widget = view.addLaser( Laser( rand_data(["A", "\\B", "C>_<"]), info={ "Name": "inv@|d", "File Path": "/invalid.npz" }, )) dlg = ExportDialog(widget) dlg.lineedit_directory.setText(str(Path("."))) assert dlg.isComplete() dlg.lineedit_filename.setText("invalid.csv") assert dlg.isComplete() dlg.check_export_all.setChecked(True) paths = dlg.generatePaths(widget.laser) assert paths[0][0].name == "invalid_A.csv" assert paths[1][0].name == "invalid__B.csv" assert paths[2][0].name == "invalid_C___.csv"
def test_standards_tool(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() data = linear_data(["A1", "B2"]) data["B2"][6] = 1.0 view.addLaser(Laser(data)) tool = StandardsTool(view.activeWidget()) view.addTab("Tool", tool) qtbot.waitExposed(tool) # Units tool.lineedit_units.setText("unit") tool.lineedit_units.editingFinished.emit() tool.combo_weighting.setCurrentIndex(2) # Table tool.spinbox_levels.setValue(5) assert not tool.isComplete() assert not tool.button_plot.isEnabled() for i in range(0, tool.table.model().rowCount()): index = tool.table.model().index(i, 0) tool.table.model().setData(index, i) # Change element, check weighting tool.combo_element.setCurrentIndex(1) assert not tool.table.isComplete() for i in range(0, tool.table.model().rowCount()): index = tool.table.model().index(i, 0) tool.table.model().setData(index, i) assert tool.isComplete() assert tool.button_plot.isEnabled() assert tool.combo_weighting.currentIndex() == 0 assert tool.lineedit_units.text() == "" tool.lineedit_units.setText("none") assert tool.calibration["B2"].gradient == 1.75 # Check weighting updates results tool.combo_weighting.setCurrentText("y") assert tool.calibration["B2"].weighting == "y" assert np.isclose(tool.calibration["B2"].gradient, 1.954022988) # Change element back, check weighting, unit, table restored tool.combo_element.setCurrentIndex(0) assert tool.isComplete() assert tool.lineedit_units.text() == "unit" assert tool.combo_weighting.currentIndex() == 2 # Test SD weighting tool.combo_weighting.setCurrentIndex(1) assert np.all(tool.calibration["A1"].weights == 4.0) tool.combo_element.setCurrentIndex(1) dlg = tool.showCurve() qtbot.waitExposed(dlg) dlg.close()
def test_tool_calculator(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() widget = view.addLaser(Laser(rand_data(["a", "b"]))) tool = CalculatorTool(view.activeWidget()) view.addTab("Tool", tool) qtbot.waitExposed(tool) assert tool.lineedit_name.text() == "calc0" tool.apply() assert "calc0" in widget.laser.elements assert tool.lineedit_name.text() == "calc1" # overwrite tool.lineedit_name.setText("a") tool.apply() assert len(widget.laser.elements) == 3 # Inserters assert tool.formula.toPlainText() == "a" tool.insertFunction(1) assert tool.formula.toPlainText() == "abs(a" tool.insertVariable(2) assert tool.formula.toPlainText() == "abs(ba" # Test output of previewData and output lineedit x = np.array(np.random.random((10, 10)), dtype=[("a", float)]) tool.formula.setPlainText("mean(a)") assert tool.previewData(x) is None assert tool.output.text() == f"{np.mean(x['a']):.10g}" # Array access in output tool.formula.setPlainText("a[0]") assert tool.previewData(x) is None assert tool.output.text() == f"{list(map('{:.4g}'.format, x['a'][0]))}" # Simple op tool.formula.setPlainText("a + 1.0") assert np.all(tool.previewData(x) == x["a"] + 1.0) assert tool.isComplete() # Invalid input tool.formula.setPlainText("fail") assert tool.previewData(x) is None assert not tool.isComplete()
def test_laser_widget_cursor(qtbot: QtBot): main = QtWidgets.QMainWindow() qtbot.addWidget(main) main.statusBar() # Create bar viewspace = LaserViewSpace() main.setCentralWidget(viewspace) view = viewspace.activeView() view.addLaser(Laser(rand_data(["a"]))) widget = view.activeWidget() # Cursor widget.updateCursorStatus(2.0, 2.0, 1.0) assert main.statusBar().currentMessage() == "2,2 [1]" widget.updateCursorStatus(1.0, 3.0, np.nan) assert main.statusBar().currentMessage() == "1,3 [nan]"
def test_laser_widget_actions(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() view.addLaser( Laser(rand_data(["a", "b"]), info={"File Path": "/home/pewpew/real.npz"})) widget = view.activeWidget() dlg = widget.actionCalibration() dlg.close() dlg = widget.actionConfig() dlg.close() widget.actionDuplicate() widget.actionCopyImage() dlg = widget.actionExport() dlg.close() dlg = widget.actionSave() dlg.close() dlg = widget.actionStatistics() dlg.close() dlg = widget.actionSelectDialog() dlg.close() dlg = widget.actionColocal() dlg.close() widget.contextMenuEvent( QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Mouse, QtCore.QPoint(0, 0))) # Test contextmenu widget.graphics.mask = np.ones((10, 10), dtype=bool) widget.contextMenuEvent( QtGui.QContextMenuEvent( QtGui.QContextMenuEvent.Mouse, widget.graphics.mapFromScene(QtCore.QPointF(0, 0)), )) widget.actionCopySelectionText() widget.actionCropSelection() dlg = widget.actionStatisticsSelection() dlg.close() dlg = widget.actionColocalSelection() dlg.close()
def test_merge_tool(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() data = rand_data(["A", "B"]) view.addLaser(Laser(data, info={"Name": "Laser 1", "File Path": "/test/laser1"})) data = rand_data(["B", "C"]) view.addLaser(Laser(data, info={"Name": "Laser 2", "File Path": "/test/laser1"})) tool = MergeTool(view.activeWidget()) view.addTab("Tool", tool) qtbot.waitExposed(tool) assert tool.list.count() == 1 # Test load via dialog dlg = tool.addLaserDialog() assert dlg.comboBoxItems() == ["Laser 2"] dlg.accept() assert tool.list.count() == 2 assert [row.offset() == (0, 0) for row in tool.list.rows] assert tool.list.rows[0].combo_element.currentText() == "A" assert tool.list.rows[1].combo_element.currentText() == "B" tool.action_align_horz.trigger() assert tool.list.rows[0].offset() == (0, 0) assert tool.list.rows[1].offset() == (0, 10) tool.action_align_vert.trigger() assert tool.list.rows[0].offset() == (0, 0) assert tool.list.rows[1].offset() == (10, 0) tool.action_align_auto.trigger() assert tool.list.rows[0].offset() == (0, 0) # Second image position unknown tool.apply()
def test_laser_view(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() laser = view.addLaser(Laser(rand_data(["A1", "B2", "C3"]))) qtbot.waitExposed(laser) view.tabs.setTabText(0, "newname") assert view.stack.widget(0).laserName() == "newname" view.contextMenuEvent( QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Mouse, QtCore.QPoint(0, 0))) # Drop event drag_mime = QtCore.QMimeData() path = Path(__file__).parent.joinpath("data", "io", "npz", "test.npz") drag_mime.setUrls([QtCore.QUrl.fromLocalFile(str(path.resolve()))]) drag_event = QtGui.QDragEnterEvent( QtCore.QPoint(0, 0), QtCore.Qt.CopyAction, drag_mime, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier, ) view.dragEnterEvent(drag_event) assert drag_event.isAccepted() drop_event = QtGui.QDropEvent( QtCore.QPoint(0, 0), QtCore.Qt.CopyAction, drag_mime, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier, ) with qtbot.waitSignal(view.numTabsChanged): view.dropEvent(drop_event) assert drop_event.isAccepted() assert len(view.widgets()) == 2 dlg = view.actionOpen() dlg.show() dlg.close()
def test_laser_widget(qtbot: QtBot): x = rand_data(["A1", "B2"]) y = x["A1"].copy() viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() view.addLaser(Laser(x)) widget = view.activeWidget() widget.applyConfig(Config(1.0, 1.0, 1.0)) assert widget.laser.config.spotsize == 1.0 widget.applyCalibration({"B2": Calibration(2.0, 2.0)}) assert widget.laser.calibration["B2"].intercept == 2.0 widget.updateNames({"A1": "A1", "B2": "2B"}) assert np.all(viewspace.uniqueElements() == ["2B", "A1"]) widget.transform(flip="horizontal") assert np.all(widget.laser.get("A1") == np.flip(y, axis=1)) widget.transform(flip="horizontal") widget.transform(flip="vertical") assert np.all(widget.laser.get("A1") == np.flip(y, axis=0)) widget.transform(flip="vertical") assert np.all(widget.laser.get("A1") == y) widget.transform(rotate="right") assert np.all(widget.laser.get("A1") == np.rot90(y, k=1, axes=(1, 0))) widget.transform(rotate="left") assert np.all(widget.laser.get("A1") == y)
def test_tool_filter(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() view.addLaser(Laser(rand_data(["a"]))) tool = FilteringTool(view.activeWidget()) view.addTab("Tool", tool) qtbot.waitExposed(tool) tool.combo_filter.setCurrentText("Mean") tool.combo_filter.activated.emit(0) tool.lineedit_fparams[0].setText("3.0") tool.lineedit_fparams[1].setText("3.0") tool.lineedit_fparams[0].editingFinished.emit() assert np.all(tool.fparams == [3.0, 3.0]) assert tool.isComplete() tool.lineedit_fparams[0].setText("5.0") tool.lineedit_fparams[0].editingFinished.emit() assert np.all(tool.fparams == [5.0, 3.0]) assert tool.isComplete() tool.combo_filter.setCurrentText("Median") tool.combo_filter.activated.emit(0) assert np.all(tool.fparams == [5.0, 3.0]) assert tool.isComplete() tool.lineedit_fparams[0].setText("4.0") tool.lineedit_fparams[0].editingFinished.emit() assert not tool.isComplete() tool.combo_filter.setCurrentText("Mean") tool.combo_filter.activated.emit(0) assert np.all(tool.fparams == [5.0, 3.0]) assert tool.isComplete() tool.apply()
def test_laser_view_space_apply_dialogs(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() qtbot.addWidget(viewspace) viewspace.views[0].addLaser(Laser(rand_data("A1"))) viewspace.refresh() dlg = viewspace.configDialog() dlg.applyPressed.emit(dlg) dlg.close() dlg = viewspace.colortableRangeDialog() dlg.applyPressed.emit(dlg) dlg.close() dlg = viewspace.fontsizeDialog() dlg.intValueSelected.emit(5) dlg.close() assert viewspace.options.font.pointSize() == 5
def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) self.resize(1280, 800) self.log = LoggingDialog() self.help = HelpDialog() self.viewspace = LaserViewSpace() self.viewspace.numTabsChanged.connect(self.updateActionAvailablity) self.viewspace.activeViewChanged.connect(self.updateActionAvailablity) self.setCentralWidget(self.viewspace) self.createActions() self.createMenus() self.statusBar().showMessage(f"Welcome to pew² version {__version__}.") self.button_status_um = QtWidgets.QRadioButton("μ") self.button_status_index = QtWidgets.QRadioButton("i") self.button_status_um.setChecked(True) self.button_status_um.toggled.connect(self.buttonStatusUnit) self.button_status_index.toggled.connect(self.buttonStatusUnit) self.statusBar().addPermanentWidget(self.button_status_um) self.statusBar().addPermanentWidget(self.button_status_index) self.updateActionAvailablity()
def test_tool_widget(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() widget = view.addLaser(Laser(rand_data("A1"), info={"Name": "Widget"})) tool = ToolWidget(widget, apply_all=True) index = widget.index widget.view.removeTab(index) widget.view.insertTab(index, "Tool", tool) qtbot.waitExposed(tool) tool.requestClose() view.tabs.tabText(index) == "Widget" with qtbot.wait_signal(tool.applyPressed): button = tool.button_box.button(QtWidgets.QDialogButtonBox.Apply) button.click() with qtbot.wait_signal(tool.applyPressed): button = tool.button_box.button(QtWidgets.QDialogButtonBox.Ok) button.click()
def test_overlay_tool(qtbot: QtBot): data = np.zeros((10, 10), dtype=[("r", float), ("g", float), ("b", float)]) data["r"][:, :] = 1.0 data["g"][:10, :] = 1.0 data["b"][:, :10] = 1.0 viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() view = viewspace.activeView() view.addLaser( Laser(data, info={ "Name": "real", "File Path": "/home/pewpew/real.npz" })) tool = OverlayTool(view.activeWidget()) view.addTab("Tool", tool) qtbot.waitExposed(tool) # Test rgb mode assert tool.rows.color_model == "rgb" tool.comboAdd(1) # r assert np.all(tool.graphics.data[0, 0] == (255 << 24) + (255 << 16)) tool.comboAdd(2) # g assert np.all(tool.graphics.data[:10] == (255 << 24) + (255 << 16) + (255 << 8)) assert np.all(tool.graphics.data[10:] == (255 << 24) + (255 << 16)) tool.comboAdd(3) # g assert np.all(tool.graphics.data[:10, :10] == (255 << 24) + (255 << 16) + (255 << 8) + 255) assert np.all(tool.graphics.data[10:, :10] == (255 << 24) + (255 << 16)) assert np.all(tool.graphics.data[10:, 10:] == (255 << 24) + (255 << 16) + (255 << 8)) assert np.all(tool.graphics.data[10:, 10:] == (255 << 24) + (255 << 16)) # Test cmyk mode tool.radio_cmyk.toggle() assert tool.rows.color_model == "cmyk" assert np.all(tool.graphics.data[:10, :10] == (255 << 24)) assert np.all(tool.graphics.data[10:, :10] == (255 << 24) + (255 << 8)) assert np.all(tool.graphics.data[10:, 10:] == (255 << 25) + 255) assert np.all(tool.graphics.data[10:, 10:] == (255 << 24) + (255 << 8) + 255) # Check that the rows are limited to 3 assert tool.rows.max_rows == 3 assert not tool.combo_add.isEnabled() assert tool.rows.rowCount() == 3 with qtbot.assert_not_emitted(tool.rows.rowsChanged): tool.addRow("r") assert tool.rows.rowCount() == 3 # Check color buttons are not enabled for row in tool.rows.rows: assert not row.button_color.isEnabled() # Test any mode tool.radio_custom.toggle() assert tool.rows.color_model == "any" assert tool.combo_add.isEnabled() assert tool.check_normalise.isEnabled() for row in tool.rows.rows: assert row.button_color.isEnabled() tool.addRow("g") tool.rows[3].setColor(QtGui.QColor.fromRgbF(0.0, 1.0, 1.0)) assert tool.rows.rowCount() == 4 # Test normalise assert np.amin(tool.graphics.data) > (255 << 24) tool.check_normalise.setChecked(True) tool.refresh() assert tool.graphics.data.min() == (255 << 24) + (255 << 8) + 255 # No red tool.check_normalise.setChecked(False) # Test export dlg = tool.openExportDialog() dlg2 = dlg.selectDirectory() dlg2.close() with tempfile.NamedTemporaryFile() as tf: dlg.export(Path(tf.name)) assert Path(tf.name).exists() with tempfile.TemporaryDirectory() as td: dlg.lineedit_directory.setText(td) dlg.lineedit_filename.setText("test.png") dlg.check_individual.setChecked(True) dlg.accept() qtbot.wait(300) assert Path(td).joinpath("test_1.png").exists() assert Path(td).joinpath("test_2.png").exists() assert Path(td).joinpath("test_3.png").exists() # Test close with qtbot.wait_signal(tool.rows.rowsChanged): tool.rows.rows[-1].close() assert tool.rows.rowCount() == 3 # Test hide tool.radio_rgb.toggle() with qtbot.wait_signal(tool.rows.rows[0].itemChanged): tool.rows.rows[0].button_hide.click() assert np.all(tool.graphics.data <= ((255 << 24) + (255 << 8) + 255)) dlg = tool.rows[0].selectColor() dlg.close()
def test_export_all_dialog(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) view = viewspace.activeView() lasers = [ Laser( rand_data("A1"), info={ "Name": "laser1", "File Path": "/fake/directory/laser1.npz" }, ), Laser( rand_data("B2"), info={ "Name": "laser2", "File Path": "/fake/directory/laser2.npz" }, ), Laser( rand_data("C3"), info={ "Name": "laser3", "File Path": "/fake/directory/laser3.npz" }, ), Laser( rand_data(["B2", "C3"]), info={ "Name": "laser4", "File Path": "/fake/directory/laser4.npz" }, ), ] widgets = [view.addLaser(laser) for laser in lasers] dlg = ExportAllDialog(widgets) dlg.open() assert dlg.lineedit_directory.text() == "/fake/directory" assert dlg.lineedit_filename.text() == "<name>.npz" assert dlg.lineedit_preview.text() == "<name>.npz" assert not dlg.isComplete() dlg.lineedit_prefix.setText("01") assert dlg.lineedit_preview.text() == "01_<name>.npz" assert not dlg.check_export_all.isEnabled() assert not dlg.check_calibrate.isEnabled() assert not dlg.combo_element.isEnabled() dlg.options.setCurrentIndex(dlg.options.indexForExt(".csv")) assert dlg.lineedit_preview.text() == "01_<name>.csv" assert dlg.check_export_all.isEnabled() assert dlg.check_calibrate.isEnabled() assert dlg.combo_element.isEnabled() dlg.check_export_all.click() assert dlg.lineedit_preview.text() == "01_<name>_<element>.csv" assert not dlg.combo_element.isEnabled() with tempfile.TemporaryDirectory() as tempdir: dlg.lineedit_directory.setText(tempdir) assert dlg.isComplete() dlg.accept() assert Path(tempdir, "01_laser1_A1.csv").exists() assert Path(tempdir, "01_laser2_B2.csv").exists() assert Path(tempdir, "01_laser3_C3.csv").exists() assert Path(tempdir, "01_laser4_B2.csv").exists() assert Path(tempdir, "01_laser4_C3.csv").exists()
class MainWindow(QtWidgets.QMainWindow): """Pewpew mainwindow, holding a LaserViewSpace. Actions for the menu and status bars are created and stored here. """ def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) self.resize(1280, 800) self.log = LoggingDialog() self.help = HelpDialog() self.viewspace = LaserViewSpace() self.viewspace.numTabsChanged.connect(self.updateActionAvailablity) self.viewspace.activeViewChanged.connect(self.updateActionAvailablity) self.setCentralWidget(self.viewspace) self.createActions() self.createMenus() self.statusBar().showMessage(f"Welcome to pew² version {__version__}.") self.button_status_um = QtWidgets.QRadioButton("μ") self.button_status_index = QtWidgets.QRadioButton("i") self.button_status_um.setChecked(True) self.button_status_um.toggled.connect(self.buttonStatusUnit) self.button_status_index.toggled.connect(self.buttonStatusUnit) self.statusBar().addPermanentWidget(self.button_status_um) self.statusBar().addPermanentWidget(self.button_status_index) self.updateActionAvailablity() def createActions(self) -> None: self.action_about = qAction("help-about", "&About", "About pew².", self.actionAbout) self.action_help = qAction("help-contents", "&Help", "Show the help contents.", self.actionHelp) self.action_colortable_range = qAction( "", "Set &Range", "Set the range of the colortable.", self.viewspace.colortableRangeDialog, ) self.action_colortable_range.setShortcut("Ctrl+R") self.action_config = qAction( "document-edit", "Default Config", "Edit the default config.", self.viewspace.configDialog, ) self.action_config.setShortcut("Ctrl+K") self.action_exit = qAction("application-exit", "Quit", "Exit the program.", self.close) self.action_exit.setShortcut("Ctrl+Shift+Q") self.action_export_all = qAction( "document-save-all", "E&xport All", "Export all open documents.", self.actionExportAll, ) self.action_export_all.setShortcut("Ctrl+Shift+X") self.action_fontsize = qAction( "insert-text", "Fontsize", "Set the font size in points.", self.viewspace.fontsizeDialog, ) self.action_group_colortable = qActionGroup( self, list(self.viewspace.options.colortables.keys()), self.actionGroupColortable, checked=self.viewspace.options.colortable, statuses=list(self.viewspace.options.colortables.values()), ) self.action_smooth = qAction( "smooth", "&Smooth", "Smooth images with bilinear interpolation.", self.viewspace.toggleSmooth, ) self.action_smooth.setCheckable(True) self.action_smooth.setChecked(self.viewspace.options.smoothing) self.action_wizard_import = qAction( "", "Import Wizard", "Start the line-wise import wizard. .", self.actionWizardImport, ) self.action_wizard_spot = qAction( "", "Spotwise Wizard", "Start the import wizard for data collected spot-wise.", self.actionWizardSpot, ) self.action_wizard_srr = qAction( "", "Kriss Kross Wizard", "Start the Super-Resolution-Reconstruction import wizard.", self.actionWizardSRR, ) self.action_log = qAction("clock", "&Show Log", "Show the pew² event and error log.", self.actionLog) self.action_open = qAction("document-open", "&Open", "Open new document(s).", self.actionOpen) self.action_open.setShortcut("Ctrl+O") self.action_toggle_calibrate = qAction( "go-top", "Ca&librate", "Toggle calibration.", self.viewspace.toggleCalibrate, ) self.action_toggle_calibrate.setShortcut("Ctrl+L") self.action_toggle_calibrate.setCheckable(True) self.action_toggle_calibrate.setChecked( self.viewspace.options.calibrate) self.action_toggle_colorbar = qAction( "", "Show Colorbar", "Toggle colorbars.", self.viewspace.setColorbarVisible) self.action_toggle_colorbar.setCheckable(True) self.action_toggle_colorbar.setChecked( self.viewspace.options.items["colorbar"]) self.action_toggle_label = qAction("", "Show Labels", "Toggle element labels.", self.viewspace.setLabelVisible) self.action_toggle_label.setCheckable(True) self.action_toggle_label.setChecked( self.viewspace.options.items["label"]) self.action_toggle_scalebar = qAction( "", "Show Scalebar", "Toggle scalebar.", self.viewspace.setScalebarVisible) self.action_toggle_scalebar.setCheckable(True) self.action_toggle_scalebar.setChecked( self.viewspace.options.items["scalebar"]) self.action_tool_calculator = qAction( "document-properties", "Calculator", "Open the calculator.", self.actionToolCalculator, ) self.action_tool_drift = qAction( "document-properties", "Drift Compensation", "Open the drift compensation tool.", self.actionToolDrift, ) self.action_tool_filter = qAction( "document-properties", "Filtering", "Open the filtering tool.", self.actionToolFilter, ) self.action_tool_merge = qAction( "align-vertical-top", "Merge Tool", "Open tool for merging multiple images.", self.actionToolMerge, ) self.action_tool_standards = qAction( "document-properties", "Calibration Standards", "Open the standards calibration tool.", self.actionToolStandards, ) self.action_tool_overlay = qAction( "document-properties", "Image Overlay", "Open the overlay tool.", self.actionToolOverlay, ) self.action_transform_flip_horizontal = qAction( "object-flip-horizontal", "Flip Horizontal", "Flip the image about vertical axis.", self.actionTransformFlipHorz, ) self.action_transform_flip_vertical = qAction( "object-flip-vertical", "Flip Vertical", "Flip the image about horizontal axis.", self.actionTransformFlipVert, ) self.action_transform_rotate_left = qAction( "object-rotate-left", "Rotate Left", "Rotate the image 90° counter clockwise.", self.actionTransformRotateLeft, ) self.action_transform_rotate_right = qAction( "object-rotate-right", "Rotate Right", "Rotate the image 90° clockwise.", self.actionTransformRotateRight, ) self.action_refresh = qAction("view-refresh", "Refresh", "Redraw documents.", self.viewspace.refresh) self.action_refresh.setShortcut("F5") def actionAbout(self) -> QtWidgets.QDialog: dlg = QtWidgets.QMessageBox( QtWidgets.QMessageBox.Information, "About pew²", ("Import, process and export of LA-ICP-MS data.\n" f"Version {__version__}\n" "Developed by the Atomic Medicine Initiative.\n" "https://github.com/djdt/pewpew"), parent=self, ) if self.windowIcon() is not None: dlg.setIconPixmap(self.windowIcon().pixmap(64, 64)) dlg.open() return dlg def actionExportAll(self) -> QtWidgets.QDialog: widgets = [ w for v in self.viewspace.views for w in v.widgets() if isinstance(w, LaserWidget) ] dlg = ExportAllDialog(widgets, self) dlg.open() return dlg def actionGroupColortable(self, action: QtWidgets.QAction) -> None: text = action.text().replace("&", "") self.viewspace.options.colortable = text self.viewspace.refresh() def actionHelp(self) -> None: self.help.show() def actionLog(self) -> None: self.log.show() def actionOpen(self) -> QtWidgets.QDialog: view = self.viewspace.activeView() return view.actionOpen() def openTool(self, tool: ToolWidget, name: str) -> None: widget = self.viewspace.activeWidget() if widget is None: return index = widget.index if isinstance(widget, ToolWidget): widget = widget.widget tool = tool(widget) name = f"{name}: {widget.laserName()}" widget.view.removeTab(index) widget.view.insertTab(index, name, tool) tool.activate() def actionToolCalculator(self) -> None: self.openTool(CalculatorTool, "Calculator") def actionToolDrift(self) -> None: self.openTool(DriftTool, "Drift") def actionToolFilter(self) -> None: self.openTool(FilteringTool, "Filter") def actionToolMerge(self) -> None: self.openTool(MergeTool, "Merge") def actionToolStandards(self) -> None: self.openTool(StandardsTool, "Standards") def actionToolOverlay(self) -> None: self.openTool(OverlayTool, "Overlay") def actionTransformFlipHorz(self) -> None: widget = self.viewspace.activeWidget() if widget is None: return widget.transform(flip="horizontal") def actionTransformFlipVert(self) -> None: widget = self.viewspace.activeWidget() if widget is None: return widget.transform(flip="vertical") def actionTransformRotateLeft(self) -> None: widget = self.viewspace.activeWidget() if widget is None: return widget.transform(rotate="left") def actionTransformRotateRight(self) -> None: widget = self.viewspace.activeWidget() if widget is None: return widget.transform(rotate="right") def actionWizardImport(self) -> QtWidgets.QWizard: wiz = ImportWizard(config=self.viewspace.config, parent=self) wiz.laserImported.connect(self.viewspace.activeView().addLaser) wiz.open() return wiz def actionWizardSpot(self) -> QtWidgets.QWizard: config = SpotConfig(self.viewspace.config.spotsize, self.viewspace.config.spotsize) wiz = SpotImportWizard(config=config, options=self.viewspace.options, parent=self) wiz.laserImported.connect(self.viewspace.activeView().addLaser) wiz.open() return wiz def actionWizardSRR(self) -> QtWidgets.QWizard: wiz = SRRImportWizard(config=self.viewspace.config, parent=self) wiz.laserImported.connect(self.viewspace.activeView().addLaser) wiz.open() return wiz def createMenus(self) -> None: # File menu_file = self.menuBar().addMenu("&File") menu_file.addAction(self.action_open) # File -> Import menu_import = menu_file.addMenu("&Import") menu_import.addAction(self.action_wizard_import) menu_import.addAction(self.action_wizard_spot) menu_import.addAction(self.action_wizard_srr) menu_file.addSeparator() menu_file.addAction(self.action_export_all) menu_file.addSeparator() menu_file.addAction(self.action_exit) # Edit menu_edit = self.menuBar().addMenu("&Edit") menu_edit.addAction(self.action_config) menu_edit.addAction(self.action_toggle_calibrate) menu_edit.addSeparator() menu_edit.addAction(self.action_transform_flip_horizontal) menu_edit.addAction(self.action_transform_flip_vertical) menu_edit.addAction(self.action_transform_rotate_left) menu_edit.addAction(self.action_transform_rotate_right) menu_tools = self.menuBar().addMenu("&Tools") menu_tools.addAction(self.action_tool_calculator) menu_tools.addAction(self.action_tool_drift) menu_tools.addAction(self.action_tool_filter) menu_tools.addAction(self.action_tool_merge) menu_tools.addAction(self.action_tool_standards) menu_tools.addAction(self.action_tool_overlay) # View menu_view = self.menuBar().addMenu("&View") menu_cmap = menu_view.addMenu("&Colortable") menu_cmap.setIcon(QtGui.QIcon.fromTheme("color-management")) menu_cmap.setStatusTip("Colortable of displayed images.") menu_cmap.addActions(self.action_group_colortable.actions()) menu_cmap.addAction(self.action_colortable_range) # View - interpolation menu_view.addAction(self.action_smooth) menu_view.addAction(self.action_fontsize) menu_view.addSeparator() menu_view.addAction(self.action_toggle_colorbar) menu_view.addAction(self.action_toggle_label) menu_view.addAction(self.action_toggle_scalebar) menu_view.addSeparator() menu_view.addAction(self.action_refresh) # Help menu_help = self.menuBar().addMenu("&Help") menu_help.addAction(self.action_log) menu_help.addAction(self.action_help) menu_help.addAction(self.action_about) def buttonStatusUnit(self, toggled: bool) -> None: """Callback for 'button_status_um'.""" if self.button_status_um.isChecked(): self.viewspace.options.units = "μm" elif self.button_status_index.isChecked(): self.viewspace.options.units = "index" def updateActionAvailablity(self) -> None: """Enables tools if at least one view is present.""" enabled = self.viewspace.countViewTabs() > 0 self.action_export_all.setEnabled(enabled) # Tools require an active view enabled = enabled and self.viewspace.activeView().tabs.count() > 0 self.action_tool_calculator.setEnabled(enabled) self.action_tool_drift.setEnabled(enabled) self.action_tool_filter.setEnabled(enabled) self.action_tool_standards.setEnabled(enabled) self.action_tool_overlay.setEnabled(enabled) def exceptHook(self, etype: type, value: BaseException, tb: TracebackType) -> None: # pragma: no cover """Redirect errors to the log.""" if etype == KeyboardInterrupt: logger.info("Keyboard interrupt, exiting.") sys.exit(1) logger.exception("Uncaught exception", exc_info=(etype, value, tb)) QtWidgets.QMessageBox.critical(self, "Uncaught Exception", str(value))
def test_export_dialog(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) view = viewspace.activeView() widget = view.addLaser( Laser(rand_data("A1"), info={ "Name": "laser", "File Path": "/home/user/laser.npz" })) dlg = ExportDialog(widget) dlg.open() assert dlg.lineedit_directory.text() == "/home/user" assert dlg.lineedit_filename.text() == "laser.npz" assert dlg.lineedit_preview.text() == "laser.npz" assert dlg.options.currentExt() == ".npz" assert not dlg.check_export_all.isEnabled() assert not dlg.check_calibrate.isEnabled() dlg.lineedit_filename.setText("laser.png") assert dlg.lineedit_preview.text() == "laser.png" assert dlg.options.currentExt() == ".png" dlg.lineedit_filename.setText("laser") assert dlg.lineedit_preview.text() == "laser.png" assert dlg.options.currentExt() == ".png" dlg.check_export_all.click() assert dlg.lineedit_preview.text() == "laser_<element>.png" dlg.lineedit_filename.setText("laser.npz") assert dlg.options.currentExt() == ".npz" assert not dlg.check_export_all.isEnabled() assert dlg.lineedit_preview.text() == "laser.npz" dlg.lineedit_filename.setText("laser.abc") assert not dlg.isComplete() dlg.lineedit_filename.setText("laser.npz") dir_dlg = dlg.selectDirectory() dir_dlg.close() dlg.lineedit_filename.setText("/fake/directory") assert not dlg.isComplete() with tempfile.TemporaryDirectory() as tempdir: # Test export dlg.lineedit_directory.setText(tempdir) dlg.lineedit_filename.setText("temp.npz") paths = dlg.generatePaths(dlg.widget) assert paths == [(Path(tempdir, "temp.npz"), "A1", None)] dlg.export(paths[0][0], paths[0][1], None, dlg.widget) assert Path(tempdir, "temp.npz").exists() # Test export all elements and png with tempfile.TemporaryDirectory() as tempdir: dlg.lineedit_directory.setText(tempdir) dlg.lineedit_filename.setText("temp.png") dlg.accept() assert Path(tempdir, "temp_A1.png").exists() with tempfile.TemporaryDirectory() as tempdir: dlg.lineedit_directory.setText(tempdir) dlg.lineedit_filename.setText("temp.vti") dlg.accept() assert Path(tempdir, "temp.vti").exists() dlg.close()
def test_laser_view_space(qtbot: QtBot): viewspace = LaserViewSpace() qtbot.addWidget(viewspace) viewspace.show() viewspace.toggleCalibrate(False) viewspace.toggleColorbar(False) viewspace.toggleLabel(False) viewspace.toggleScalebar(False) viewspace.toggleSmooth(False) assert not viewspace.options.calibrate assert not viewspace.options.items["colorbar"] assert not viewspace.options.items["label"] assert not viewspace.options.items["scalebar"] assert not viewspace.options.smoothing viewspace.splitActiveHorizontal() assert viewspace.currentElement() is None viewspace.views[0].addLaser(Laser(rand_data(["A1", "B2"]))) viewspace.views[0].addLaser(Laser(rand_data(["A1", "C3"]))) viewspace.views[1].addLaser(Laser(rand_data(["A1", "C3"]))) viewspace.views[1].addLaser(Laser(rand_data(["B2", "D4"]))) assert viewspace.uniqueElements() == ["A1", "B2", "C3", "D4"] assert viewspace.currentElement() == "A1" # Apply config viewspace.applyConfig(Config(10, 10, 10)) for view in viewspace.views: for widget in view.widgets(): assert widget.laser.config.spotsize == 10 assert widget.laser.config.speed == 10 assert widget.laser.config.scantime == 10 # Try to apply calibraiton viewspace.applyCalibration({ "A1": Calibration(1.0, 1.0), "B2": Calibration(2.0, 2.0) }) qtbot.waitExposed(viewspace) for view in viewspace.views: for widget in view.widgets(): if "A1" in widget.laser.elements: assert widget.laser.calibration["A1"].intercept == 1.0 assert widget.laser.calibration["A1"].gradient == 1.0 if "B2" in widget.laser.elements: assert widget.laser.calibration["B2"].intercept == 2.0 assert widget.laser.calibration["B2"].gradient == 2.0 if "C3" in widget.laser.elements: assert widget.laser.calibration["C3"].intercept == 0.0 assert widget.laser.calibration["C3"].gradient == 1.0 # Check element changed if avilable assert viewspace.views[0].activeWidget().combo_element.currentText( ) == "A1" assert viewspace.views[1].activeWidget().combo_element.currentText( ) == "A1" viewspace.setCurrentElement("B2") assert viewspace.views[0].activeWidget().combo_element.currentText( ) == "B2" assert viewspace.views[1].activeWidget().combo_element.currentText( ) == "A1" # Close all for view in viewspace.views: for widget in view.widgets(): widget.close()