diff options
Diffstat (limited to 'examples/external')
31 files changed, 1259 insertions, 0 deletions
diff --git a/examples/external/matplotlib/widget3d/doc/widget3d.png b/examples/external/matplotlib/widget3d/doc/widget3d.png Binary files differnew file mode 100644 index 000000000..fa2ed5043 --- /dev/null +++ b/examples/external/matplotlib/widget3d/doc/widget3d.png diff --git a/examples/external/matplotlib/widget3d/doc/widget3d.rst b/examples/external/matplotlib/widget3d/doc/widget3d.rst new file mode 100644 index 000000000..b5c9fd8fd --- /dev/null +++ b/examples/external/matplotlib/widget3d/doc/widget3d.rst @@ -0,0 +1,9 @@ +Matplotlib Widget 3D Example +============================ + +A Python application that demonstrates how to combine matplotlib +with Qt Widget-based functionality. + +.. image:: widget3d.png + :width: 400 + :alt: Matplotlib Widget 3D Screenshot diff --git a/examples/external/matplotlib/widget3d/requirements.txt b/examples/external/matplotlib/widget3d/requirements.txt new file mode 100644 index 000000000..db5d81e01 --- /dev/null +++ b/examples/external/matplotlib/widget3d/requirements.txt @@ -0,0 +1,2 @@ +matplotlib +numpy diff --git a/examples/external/matplotlib/widget3d/widget3d.py b/examples/external/matplotlib/widget3d/widget3d.py new file mode 100644 index 000000000..8bfcc4ca2 --- /dev/null +++ b/examples/external/matplotlib/widget3d/widget3d.py @@ -0,0 +1,207 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys + +import numpy as np +from matplotlib.backends.backend_qtagg import FigureCanvas +from matplotlib.figure import Figure +from mpl_toolkits.mplot3d import axes3d +from PySide6.QtCore import Qt, Slot +from PySide6.QtGui import QAction, QKeySequence +from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, + QHeaderView, QLabel, QMainWindow, QSlider, + QTableWidget, QTableWidgetItem, QVBoxLayout, + QWidget) + + +"""This example implements the interaction between Qt Widgets and a 3D +matplotlib plot""" + + +class ApplicationWindow(QMainWindow): + def __init__(self, parent=None): + QMainWindow.__init__(self, parent) + + self.column_names = ["Column A", "Column B", "Column C"] + + # Central widget + self._main = QWidget() + self.setCentralWidget(self._main) + + # Main menu bar + self.menu = self.menuBar() + self.menu_file = self.menu.addMenu("File") + exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821 + self.menu_file.addAction(exit) + + self.menu_about = self.menu.addMenu("&About") + about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), + triggered=qApp.aboutQt) # noqa: F821 + self.menu_about.addAction(about) + + # Figure (Left) + self.fig = Figure(figsize=(5, 3)) + self.canvas = FigureCanvas(self.fig) + + # Sliders (Left) + min = 0 + max = 360 + self.slider_azim = QSlider(minimum=min, maximum=max, orientation=Qt.Horizontal) + self.slider_elev = QSlider(minimum=min, maximum=max, orientation=Qt.Horizontal) + + self.slider_azim_layout = QHBoxLayout() + self.slider_azim_layout.addWidget(QLabel(f"{min}")) + self.slider_azim_layout.addWidget(self.slider_azim) + self.slider_azim_layout.addWidget(QLabel(f"{max}")) + + self.slider_elev_layout = QHBoxLayout() + self.slider_elev_layout.addWidget(QLabel(f"{min}")) + self.slider_elev_layout.addWidget(self.slider_elev) + self.slider_elev_layout.addWidget(QLabel(f"{max}")) + + # Table (Right) + self.table = QTableWidget() + header = self.table.horizontalHeader() + header.setSectionResizeMode(QHeaderView.Stretch) + + # ComboBox (Right) + self.combo = QComboBox() + self.combo.addItems(["Wired", "Surface", "Triangular Surface", "Sphere"]) + + # Right layout + rlayout = QVBoxLayout() + rlayout.setContentsMargins(1, 1, 1, 1) + rlayout.addWidget(QLabel("Plot type:")) + rlayout.addWidget(self.combo) + rlayout.addWidget(self.table) + + # Left layout + llayout = QVBoxLayout() + rlayout.setContentsMargins(1, 1, 1, 1) + llayout.addWidget(self.canvas, 88) + llayout.addWidget(QLabel("Azimuth:"), 1) + llayout.addLayout(self.slider_azim_layout, 5) + llayout.addWidget(QLabel("Elevation:"), 1) + llayout.addLayout(self.slider_elev_layout, 5) + + # Main layout + layout = QHBoxLayout(self._main) + layout.addLayout(llayout, 70) + layout.addLayout(rlayout, 30) + + # Signal and Slots connections + self.combo.currentTextChanged.connect(self.combo_option) + self.slider_azim.valueChanged.connect(self.rotate_azim) + self.slider_elev.valueChanged.connect(self.rotate_elev) + + # Initial setup + self.plot_wire() + self._ax.view_init(30, 30) + self.slider_azim.setValue(30) + self.slider_elev.setValue(30) + self.fig.canvas.mpl_connect("button_release_event", self.on_click) + + # Matplotlib slot method + def on_click(self, event): + azim, elev = self._ax.azim, self._ax.elev + self.slider_azim.setValue(azim + 180) + self.slider_elev.setValue(elev + 180) + + # Utils methods + + def set_table_data(self, X, Y, Z): + for i in range(len(X)): + self.table.setItem(i, 0, QTableWidgetItem(f"{X[i]:.2f}")) + self.table.setItem(i, 1, QTableWidgetItem(f"{Y[i]:.2f}")) + self.table.setItem(i, 2, QTableWidgetItem(f"{Z[i]:.2f}")) + + def set_canvas_table_configuration(self, row_count, data): + self.fig.set_canvas(self.canvas) + self._ax = self.canvas.figure.add_subplot(projection="3d") + + self._ax.set_xlabel(self.column_names[0]) + self._ax.set_ylabel(self.column_names[1]) + self._ax.set_zlabel(self.column_names[2]) + + self.table.setRowCount(row_count) + self.table.setColumnCount(3) + self.table.setHorizontalHeaderLabels(self.column_names) + self.set_table_data(data[0], data[1], data[2]) + + # Plot methods + + def plot_wire(self): + # Data + self.X, self.Y, self.Z = axes3d.get_test_data(0.03) + + self.set_canvas_table_configuration(len(self.X[0]), (self.X[0], self.Y[0], self.Z[0])) + self._ax.plot_wireframe(self.X, self.Y, self.Z, rstride=10, cstride=10, cmap="viridis") + self.canvas.draw() + + def plot_surface(self): + # Data + self.X, self.Y = np.meshgrid(np.linspace(-6, 6, 30), np.linspace(-6, 6, 30)) + self.Z = np.sin(np.sqrt(self.X ** 2 + self.Y ** 2)) + + self.set_canvas_table_configuration(len(self.X[0]), (self.X[0], self.Y[0], self.Z[0])) + self._ax.plot_surface(self.X, self.Y, self.Z, + rstride=1, cstride=1, cmap="viridis", edgecolor="none") + self.canvas.draw() + + def plot_triangular_surface(self): + # Data + radii = np.linspace(0.125, 1.0, 8) + angles = np.linspace(0, 2 * np.pi, 36, endpoint=False)[..., np.newaxis] + self.X = np.append(0, (radii * np.cos(angles)).flatten()) + self.Y = np.append(0, (radii * np.sin(angles)).flatten()) + self.Z = np.sin(-self.X * self.Y) + + self.set_canvas_table_configuration(len(self.X), (self.X, self.Y, self.Z)) + self._ax.plot_trisurf(self.X, self.Y, self.Z, linewidth=0.2, antialiased=True) + self.canvas.draw() + + def plot_sphere(self): + # Data + u = np.linspace(0, 2 * np.pi, 100) + v = np.linspace(0, np.pi, 100) + self.X = 10 * np.outer(np.cos(u), np.sin(v)) + self.Y = 10 * np.outer(np.sin(u), np.sin(v)) + self.Z = 9 * np.outer(np.ones(np.size(u)), np.cos(v)) + + self.set_canvas_table_configuration(len(self.X), (self.X[0], self.Y[0], self.Z[0])) + self._ax.plot_surface(self.X, self.Y, self.Z) + self.canvas.draw() + + # Slots + + @Slot() + def combo_option(self, text): + if text == "Wired": + self.plot_wire() + elif text == "Surface": + self.plot_surface() + elif text == "Triangular Surface": + self.plot_triangular_surface() + elif text == "Sphere": + self.plot_sphere() + + @Slot() + def rotate_azim(self, value): + self._ax.view_init(self._ax.elev, value) + self.fig.set_canvas(self.canvas) + self.canvas.draw() + + @Slot() + def rotate_elev(self, value): + self._ax.view_init(value, self._ax.azim) + self.fig.set_canvas(self.canvas) + self.canvas.draw() + + +if __name__ == "__main__": + app = QApplication(sys.argv) + w = ApplicationWindow() + w.setFixedSize(1280, 720) + w.show() + app.exec() diff --git a/examples/external/matplotlib/widget3d/widget3d.pyproject b/examples/external/matplotlib/widget3d/widget3d.pyproject new file mode 100644 index 000000000..6b25c5be7 --- /dev/null +++ b/examples/external/matplotlib/widget3d/widget3d.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["widget3d.py"] +} diff --git a/examples/external/matplotlib/widget_gaussian/doc/widget_gaussian.png b/examples/external/matplotlib/widget_gaussian/doc/widget_gaussian.png Binary files differnew file mode 100644 index 000000000..e9fe16354 --- /dev/null +++ b/examples/external/matplotlib/widget_gaussian/doc/widget_gaussian.png diff --git a/examples/external/matplotlib/widget_gaussian/doc/widget_gaussian.rst b/examples/external/matplotlib/widget_gaussian/doc/widget_gaussian.rst new file mode 100644 index 000000000..467fb42c2 --- /dev/null +++ b/examples/external/matplotlib/widget_gaussian/doc/widget_gaussian.rst @@ -0,0 +1,9 @@ +Matplotlib Widget Gaussian Example +================================== + +A Python application that demonstrates how to interact with +matplotlib and scipy, combined with Qt Widgets. + +.. image:: widget_gaussian.png + :width: 400 + :alt: Matplotlib Widget Gaussian Screenshot diff --git a/examples/external/matplotlib/widget_gaussian/requirements.txt b/examples/external/matplotlib/widget_gaussian/requirements.txt new file mode 100644 index 000000000..26a2b438f --- /dev/null +++ b/examples/external/matplotlib/widget_gaussian/requirements.txt @@ -0,0 +1,3 @@ +matplotlib +scipy +numpy diff --git a/examples/external/matplotlib/widget_gaussian/widget_gaussian.py b/examples/external/matplotlib/widget_gaussian/widget_gaussian.py new file mode 100644 index 000000000..2423e496a --- /dev/null +++ b/examples/external/matplotlib/widget_gaussian/widget_gaussian.py @@ -0,0 +1,74 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys + +import numpy as np +from scipy.stats import norm +from matplotlib.figure import Figure +from matplotlib.backends.backend_qtagg import FigureCanvas +from matplotlib.backends.backend_qtagg import NavigationToolbar2QT +from PySide6.QtCore import Slot +from PySide6.QtWidgets import ( + QApplication, + QWidget, + QDoubleSpinBox, + QVBoxLayout, + QHBoxLayout, +) + + +"""This example implements the interaction between Qt Widgets and a 2D +matplotlib plot showing a gaussian curve with scipy""" + + +class PlotWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + # create widgets + self.view = FigureCanvas(Figure(figsize=(5, 3))) + self.axes = self.view.figure.subplots() + self.toolbar = NavigationToolbar2QT(self.view, self) + self.mu_input = QDoubleSpinBox() + self.std_input = QDoubleSpinBox() + self.mu_input.setPrefix("μ: ") + self.std_input.setPrefix("σ: ") + self.std_input.setValue(10) + + # Create layout + input_layout = QHBoxLayout() + input_layout.addWidget(self.mu_input) + input_layout.addWidget(self.std_input) + vlayout = QVBoxLayout() + vlayout.addWidget(self.toolbar) + vlayout.addWidget(self.view) + vlayout.addLayout(input_layout) + self.setLayout(vlayout) + + # connect inputs with on_change method + self.mu_input.valueChanged.connect(self.on_change) + self.std_input.valueChanged.connect(self.on_change) + + self.on_change() + + @Slot() + def on_change(self): + """ Update the plot with the current input values """ + mu = self.mu_input.value() + std = self.std_input.value() + + x = np.linspace(-100, 100) + y = norm.pdf(x, mu, std) + + self.axes.clear() + self.axes.plot(x, y) + self.view.draw() + + +if __name__ == "__main__": + + app = QApplication(sys.argv) + w = PlotWidget() + w.show() + sys.exit(app.exec()) diff --git a/examples/external/matplotlib/widget_gaussian/widget_gaussian.pyproject b/examples/external/matplotlib/widget_gaussian/widget_gaussian.pyproject new file mode 100644 index 000000000..72c5adc78 --- /dev/null +++ b/examples/external/matplotlib/widget_gaussian/widget_gaussian.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["widget_gaussian.py"] +} diff --git a/examples/external/networkx/doc/networkx.png b/examples/external/networkx/doc/networkx.png Binary files differnew file mode 100644 index 000000000..d3264f8ee --- /dev/null +++ b/examples/external/networkx/doc/networkx.png diff --git a/examples/external/networkx/doc/networkx.rst b/examples/external/networkx/doc/networkx.rst new file mode 100644 index 000000000..58897d3b7 --- /dev/null +++ b/examples/external/networkx/doc/networkx.rst @@ -0,0 +1,8 @@ +Networkx viewer Example +======================= + +A Python application that demonstrates how to display networkx graph into a QGraphicsView. + +.. image:: networkx.png + :width: 400 + :alt: Networkx viewer Screenshot diff --git a/examples/external/networkx/main.py b/examples/external/networkx/main.py new file mode 100644 index 000000000..8cd7e7903 --- /dev/null +++ b/examples/external/networkx/main.py @@ -0,0 +1,346 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + +import math +import sys + +from PySide6.QtCore import (QEasingCurve, QLineF, + QParallelAnimationGroup, QPointF, + QPropertyAnimation, QRectF, Qt) +from PySide6.QtGui import QBrush, QColor, QPainter, QPen, QPolygonF +from PySide6.QtWidgets import (QApplication, QComboBox, QGraphicsItem, + QGraphicsObject, QGraphicsScene, QGraphicsView, + QStyleOptionGraphicsItem, QVBoxLayout, QWidget) + +import networkx as nx + + +class Node(QGraphicsObject): + + """A QGraphicsItem representing node in a graph""" + + def __init__(self, name: str, parent=None): + """Node constructor + + Args: + name (str): Node label + """ + super().__init__(parent) + self._name = name + self._edges = [] + self._color = "#5AD469" + self._radius = 30 + self._rect = QRectF(0, 0, self._radius * 2, self._radius * 2) + + self.setFlag(QGraphicsItem.ItemIsMovable) + self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) + self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + + def boundingRect(self) -> QRectF: + """Override from QGraphicsItem + + Returns: + QRect: Return node bounding rect + """ + return self._rect + + def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget = None): + """Override from QGraphicsItem + + Draw node + + Args: + painter (QPainter) + option (QStyleOptionGraphicsItem) + """ + painter.setRenderHints(QPainter.Antialiasing) + painter.setPen( + QPen( + QColor(self._color).darker(), + 2, + Qt.SolidLine, + Qt.RoundCap, + Qt.RoundJoin, + ) + ) + painter.setBrush(QBrush(QColor(self._color))) + painter.drawEllipse(self.boundingRect()) + painter.setPen(QPen(QColor("white"))) + painter.drawText(self.boundingRect(), Qt.AlignCenter, self._name) + + def add_edge(self, edge): + """Add an edge to this node + + Args: + edge (Edge) + """ + self._edges.append(edge) + + def itemChange(self, change: QGraphicsItem.GraphicsItemChange, value): + """Override from QGraphicsItem + + Args: + change (QGraphicsItem.GraphicsItemChange) + value (Any) + + Returns: + Any + """ + if change == QGraphicsItem.ItemPositionHasChanged: + for edge in self._edges: + edge.adjust() + + return super().itemChange(change, value) + + +class Edge(QGraphicsItem): + def __init__(self, source: Node, dest: Node, parent: QGraphicsItem = None): + """Edge constructor + + Args: + source (Node): source node + dest (Node): destination node + """ + super().__init__(parent) + self._source = source + self._dest = dest + + self._tickness = 2 + self._color = "#2BB53C" + self._arrow_size = 20 + + self._source.add_edge(self) + self._dest.add_edge(self) + + self._line = QLineF() + self.setZValue(-1) + self.adjust() + + def boundingRect(self) -> QRectF: + """Override from QGraphicsItem + + Returns: + QRect: Return node bounding rect + """ + return ( + QRectF(self._line.p1(), self._line.p2()) + .normalized() + .adjusted( + -self._tickness - self._arrow_size, + -self._tickness - self._arrow_size, + self._tickness + self._arrow_size, + self._tickness + self._arrow_size, + ) + ) + + def adjust(self): + """ + Update edge position from source and destination node. + This method is called from Node::itemChange + """ + self.prepareGeometryChange() + self._line = QLineF( + self._source.pos() + self._source.boundingRect().center(), + self._dest.pos() + self._dest.boundingRect().center(), + ) + + def _draw_arrow(self, painter: QPainter, start: QPointF, end: QPointF): + """Draw arrow from start point to end point. + + Args: + painter (QPainter) + start (QPointF): start position + end (QPointF): end position + """ + painter.setBrush(QBrush(self._color)) + + line = QLineF(end, start) + + angle = math.atan2(-line.dy(), line.dx()) + arrow_p1 = line.p1() + QPointF( + math.sin(angle + math.pi / 3) * self._arrow_size, + math.cos(angle + math.pi / 3) * self._arrow_size, + ) + arrow_p2 = line.p1() + QPointF( + math.sin(angle + math.pi - math.pi / 3) * self._arrow_size, + math.cos(angle + math.pi - math.pi / 3) * self._arrow_size, + ) + + arrow_head = QPolygonF() + arrow_head.clear() + arrow_head.append(line.p1()) + arrow_head.append(arrow_p1) + arrow_head.append(arrow_p2) + painter.drawLine(line) + painter.drawPolygon(arrow_head) + + def _arrow_target(self) -> QPointF: + """Calculate the position of the arrow taking into account the size of the destination node + + Returns: + QPointF + """ + target = self._line.p1() + center = self._line.p2() + radius = self._dest._radius + vector = target - center + length = math.sqrt(vector.x() ** 2 + vector.y() ** 2) + if length == 0: + return target + normal = vector / length + target = QPointF(center.x() + (normal.x() * radius), center.y() + (normal.y() * radius)) + + return target + + def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget=None): + """Override from QGraphicsItem + + Draw Edge. This method is called from Edge.adjust() + + Args: + painter (QPainter) + option (QStyleOptionGraphicsItem) + """ + + if self._source and self._dest: + painter.setRenderHints(QPainter.Antialiasing) + + painter.setPen( + QPen( + QColor(self._color), + self._tickness, + Qt.SolidLine, + Qt.RoundCap, + Qt.RoundJoin, + ) + ) + painter.drawLine(self._line) + self._draw_arrow(painter, self._line.p1(), self._arrow_target()) + self._arrow_target() + + +class GraphView(QGraphicsView): + def __init__(self, graph: nx.DiGraph, parent=None): + """GraphView constructor + + This widget can display a directed graph + + Args: + graph (nx.DiGraph): a networkx directed graph + """ + super().__init__() + self._graph = graph + self._scene = QGraphicsScene() + self.setScene(self._scene) + + # Used to add space between nodes + self._graph_scale = 200 + + # Map node name to Node object {str=>Node} + self._nodes_map = {} + + # List of networkx layout function + self._nx_layout = { + "circular": nx.circular_layout, + "planar": nx.planar_layout, + "random": nx.random_layout, + "shell_layout": nx.shell_layout, + "kamada_kawai_layout": nx.kamada_kawai_layout, + "spring_layout": nx.spring_layout, + "spiral_layout": nx.spiral_layout, + } + + self._load_graph() + self.set_nx_layout("circular") + + def get_nx_layouts(self) -> list: + """Return all layout names + + Returns: + list: layout name (str) + """ + return self._nx_layout.keys() + + def set_nx_layout(self, name: str): + """Set networkx layout and start animation + + Args: + name (str): Layout name + """ + if name in self._nx_layout: + self._nx_layout_function = self._nx_layout[name] + + # Compute node position from layout function + positions = self._nx_layout_function(self._graph) + + # Change position of all nodes using an animation + self.animations = QParallelAnimationGroup() + for node, pos in positions.items(): + x, y = pos + x *= self._graph_scale + y *= self._graph_scale + item = self._nodes_map[node] + + animation = QPropertyAnimation(item, b"pos") + animation.setDuration(1000) + animation.setEndValue(QPointF(x, y)) + animation.setEasingCurve(QEasingCurve.OutExpo) + self.animations.addAnimation(animation) + + self.animations.start() + + def _load_graph(self): + """Load graph into QGraphicsScene using Node class and Edge class""" + + self.scene().clear() + self._nodes_map.clear() + + # Add nodes + for node in self._graph: + item = Node(node) + self.scene().addItem(item) + self._nodes_map[node] = item + + # Add edges + for a, b in self._graph.edges: + source = self._nodes_map[a] + dest = self._nodes_map[b] + self.scene().addItem(Edge(source, dest)) + + +class MainWindow(QWidget): + def __init__(self, parent=None): + super().__init__() + + self.graph = nx.DiGraph() + self.graph.add_edges_from( + [ + ("1", "2"), + ("2", "3"), + ("3", "4"), + ("1", "5"), + ("1", "6"), + ("1", "7"), + ] + ) + + self.view = GraphView(self.graph) + self.choice_combo = QComboBox() + self.choice_combo.addItems(self.view.get_nx_layouts()) + v_layout = QVBoxLayout(self) + v_layout.addWidget(self.choice_combo) + v_layout.addWidget(self.view) + self.choice_combo.currentTextChanged.connect(self.view.set_nx_layout) + + +if __name__ == "__main__": + + app = QApplication(sys.argv) + + # Create a networkx graph + + widget = MainWindow() + widget.show() + widget.resize(800, 600) + sys.exit(app.exec()) diff --git a/examples/external/networkx/networkx.pyproject b/examples/external/networkx/networkx.pyproject new file mode 100644 index 000000000..cc7a74a34 --- /dev/null +++ b/examples/external/networkx/networkx.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py"] +} diff --git a/examples/external/networkx/requirements.txt b/examples/external/networkx/requirements.txt new file mode 100644 index 000000000..370ba57d9 --- /dev/null +++ b/examples/external/networkx/requirements.txt @@ -0,0 +1,3 @@ +networkx +numpy +scipy diff --git a/examples/external/opencv/doc/opencv.png b/examples/external/opencv/doc/opencv.png Binary files differnew file mode 100644 index 000000000..e3edcab47 --- /dev/null +++ b/examples/external/opencv/doc/opencv.png diff --git a/examples/external/opencv/doc/opencv.rst b/examples/external/opencv/doc/opencv.rst new file mode 100644 index 000000000..ff2c72501 --- /dev/null +++ b/examples/external/opencv/doc/opencv.rst @@ -0,0 +1,9 @@ +OpenCV Face Detection Example +============================= + +A Python application that demonstrates how to use OpenCV +and a trained model to detect faces detected from a webcam. + +.. image:: opencv.png + :width: 400 + :alt: OpenCV Face Detection Screenshot diff --git a/examples/external/opencv/opencv.pyproject b/examples/external/opencv/opencv.pyproject new file mode 100644 index 000000000..dd9a73e24 --- /dev/null +++ b/examples/external/opencv/opencv.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["webcam_pattern_detection.py"] +} diff --git a/examples/external/opencv/requirements.txt b/examples/external/opencv/requirements.txt new file mode 100644 index 000000000..0dd006bbc --- /dev/null +++ b/examples/external/opencv/requirements.txt @@ -0,0 +1 @@ +opencv-python diff --git a/examples/external/opencv/webcam_pattern_detection.py b/examples/external/opencv/webcam_pattern_detection.py new file mode 100644 index 000000000..0c55a1333 --- /dev/null +++ b/examples/external/opencv/webcam_pattern_detection.py @@ -0,0 +1,170 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import os +import sys +import time + +import cv2 +from PySide6.QtCore import Qt, QThread, Signal, Slot +from PySide6.QtGui import QAction, QImage, QKeySequence, QPixmap +from PySide6.QtWidgets import (QApplication, QComboBox, QGroupBox, + QHBoxLayout, QLabel, QMainWindow, QPushButton, + QSizePolicy, QVBoxLayout, QWidget) + + +"""This example uses the video from a webcam to apply pattern +detection from the OpenCV module. e.g.: face, eyes, body, etc.""" + + +class Thread(QThread): + updateFrame = Signal(QImage) + + def __init__(self, parent=None): + QThread.__init__(self, parent) + self.trained_file = None + self.status = True + self.cap = True + + def set_file(self, fname): + # The data comes with the 'opencv-python' module + self.trained_file = os.path.join(cv2.data.haarcascades, fname) + + def run(self): + self.cap = cv2.VideoCapture(0) + while self.status: + cascade = cv2.CascadeClassifier(self.trained_file) + ret, frame = self.cap.read() + if not ret: + continue + + # Reading frame in gray scale to process the pattern + gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + + detections = cascade.detectMultiScale(gray_frame, scaleFactor=1.1, + minNeighbors=5, minSize=(30, 30)) + + # Drawing green rectangle around the pattern + for (x, y, w, h) in detections: + pos_ori = (x, y) + pos_end = (x + w, y + h) + color = (0, 255, 0) + cv2.rectangle(frame, pos_ori, pos_end, color, 2) + + # Reading the image in RGB to display it + color_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + # Creating and scaling QImage + h, w, ch = color_frame.shape + img = QImage(color_frame.data, w, h, ch * w, QImage.Format_RGB888) + scaled_img = img.scaled(640, 480, Qt.KeepAspectRatio) + + # Emit signal + self.updateFrame.emit(scaled_img) + sys.exit(-1) + + +class Window(QMainWindow): + def __init__(self): + super().__init__() + # Title and dimensions + self.setWindowTitle("Patterns detection") + self.setGeometry(0, 0, 800, 500) + + # Main menu bar + self.menu = self.menuBar() + self.menu_file = self.menu.addMenu("File") + exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821 + self.menu_file.addAction(exit) + + self.menu_about = self.menu.addMenu("&About") + about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), + triggered=qApp.aboutQt) # noqa: F821 + self.menu_about.addAction(about) + + # Create a label for the display camera + self.label = QLabel(self) + self.label.setFixedSize(640, 480) + + # Thread in charge of updating the image + self.th = Thread(self) + self.th.finished.connect(self.close) + self.th.updateFrame.connect(self.setImage) + + # Model group + self.group_model = QGroupBox("Trained model") + self.group_model.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + model_layout = QHBoxLayout() + + self.combobox = QComboBox() + for xml_file in os.listdir(cv2.data.haarcascades): + if xml_file.endswith(".xml"): + self.combobox.addItem(xml_file) + + model_layout.addWidget(QLabel("File:"), 10) + model_layout.addWidget(self.combobox, 90) + self.group_model.setLayout(model_layout) + + # Buttons layout + buttons_layout = QHBoxLayout() + self.button1 = QPushButton("Start") + self.button2 = QPushButton("Stop/Close") + self.button1.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + self.button2.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + buttons_layout.addWidget(self.button2) + buttons_layout.addWidget(self.button1) + + right_layout = QHBoxLayout() + right_layout.addWidget(self.group_model, 1) + right_layout.addLayout(buttons_layout, 1) + + # Main layout + layout = QVBoxLayout() + layout.addWidget(self.label) + layout.addLayout(right_layout) + + # Central widget + widget = QWidget(self) + widget.setLayout(layout) + self.setCentralWidget(widget) + + # Connections + self.button1.clicked.connect(self.start) + self.button2.clicked.connect(self.kill_thread) + self.button2.setEnabled(False) + self.combobox.currentTextChanged.connect(self.set_model) + + @Slot() + def set_model(self, text): + self.th.set_file(text) + + @Slot() + def kill_thread(self): + print("Finishing...") + self.button2.setEnabled(False) + self.button1.setEnabled(True) + self.th.cap.release() + cv2.destroyAllWindows() + self.status = False + self.th.terminate() + # Give time for the thread to finish + time.sleep(1) + + @Slot() + def start(self): + print("Starting...") + self.button2.setEnabled(True) + self.button1.setEnabled(False) + self.th.set_file(self.combobox.currentText()) + self.th.start() + + @Slot(QImage) + def setImage(self, image): + self.label.setPixmap(QPixmap.fromImage(image)) + + +if __name__ == "__main__": + app = QApplication() + w = Window() + w.show() + sys.exit(app.exec()) diff --git a/examples/external/pandas/dataframe_model.py b/examples/external/pandas/dataframe_model.py new file mode 100644 index 000000000..b3d9e81fe --- /dev/null +++ b/examples/external/pandas/dataframe_model.py @@ -0,0 +1,82 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import pandas as pd + +from PySide6.QtWidgets import QTableView, QApplication +from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex +import sys + + +class PandasModel(QAbstractTableModel): + """A model to interface a Qt view with pandas dataframe """ + + def __init__(self, dataframe: pd.DataFrame, parent=None): + QAbstractTableModel.__init__(self, parent) + self._dataframe = dataframe + + def rowCount(self, parent=QModelIndex()) -> int: + """ Override method from QAbstractTableModel + + Return row count of the pandas DataFrame + """ + if parent == QModelIndex(): + return len(self._dataframe) + + return 0 + + def columnCount(self, parent=QModelIndex()) -> int: + """Override method from QAbstractTableModel + + Return column count of the pandas DataFrame + """ + if parent == QModelIndex(): + return len(self._dataframe.columns) + return 0 + + def data(self, index: QModelIndex, role=Qt.ItemDataRole): + """Override method from QAbstractTableModel + + Return data cell from the pandas DataFrame + """ + if not index.isValid(): + return None + + if role == Qt.DisplayRole: + return str(self._dataframe.iloc[index.row(), index.column()]) + + return None + + def headerData( + self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole + ): + """Override method from QAbstractTableModel + + Return dataframe index as vertical header data and columns as horizontal header data. + """ + if role == Qt.DisplayRole: + if orientation == Qt.Horizontal: + return str(self._dataframe.columns[section]) + + if orientation == Qt.Vertical: + return str(self._dataframe.index[section]) + + return None + + +if __name__ == "__main__": + + app = QApplication(sys.argv) + + df = pd.read_csv("iris.csv") + + view = QTableView() + view.resize(800, 500) + view.horizontalHeader().setStretchLastSection(True) + view.setAlternatingRowColors(True) + view.setSelectionBehavior(QTableView.SelectRows) + + model = PandasModel(df) + view.setModel(model) + view.show() + app.exec() diff --git a/examples/external/pandas/doc/pandas.rst b/examples/external/pandas/doc/pandas.rst new file mode 100644 index 000000000..8e75eead4 --- /dev/null +++ b/examples/external/pandas/doc/pandas.rst @@ -0,0 +1,9 @@ +Pandas Simple Example +===================== + +A Python application that demonstrates how to visualize +a Pandas DataFrame. + +.. image:: pandas_simple.png + :width: 400 + :alt: Pandas Simple Screenshot diff --git a/examples/external/pandas/doc/pandas_simple.png b/examples/external/pandas/doc/pandas_simple.png Binary files differnew file mode 100644 index 000000000..ea5d240bd --- /dev/null +++ b/examples/external/pandas/doc/pandas_simple.png diff --git a/examples/external/pandas/iris.csv b/examples/external/pandas/iris.csv new file mode 100644 index 000000000..bf14e161b --- /dev/null +++ b/examples/external/pandas/iris.csv @@ -0,0 +1,151 @@ +"sepal.length","sepal.width","petal.length","petal.width","variety" +5.1,3.5,1.4,.2,"Setosa" +4.9,3,1.4,.2,"Setosa" +4.7,3.2,1.3,.2,"Setosa" +4.6,3.1,1.5,.2,"Setosa" +5,3.6,1.4,.2,"Setosa" +5.4,3.9,1.7,.4,"Setosa" +4.6,3.4,1.4,.3,"Setosa" +5,3.4,1.5,.2,"Setosa" +4.4,2.9,1.4,.2,"Setosa" +4.9,3.1,1.5,.1,"Setosa" +5.4,3.7,1.5,.2,"Setosa" +4.8,3.4,1.6,.2,"Setosa" +4.8,3,1.4,.1,"Setosa" +4.3,3,1.1,.1,"Setosa" +5.8,4,1.2,.2,"Setosa" +5.7,4.4,1.5,.4,"Setosa" +5.4,3.9,1.3,.4,"Setosa" +5.1,3.5,1.4,.3,"Setosa" +5.7,3.8,1.7,.3,"Setosa" +5.1,3.8,1.5,.3,"Setosa" +5.4,3.4,1.7,.2,"Setosa" +5.1,3.7,1.5,.4,"Setosa" +4.6,3.6,1,.2,"Setosa" +5.1,3.3,1.7,.5,"Setosa" +4.8,3.4,1.9,.2,"Setosa" +5,3,1.6,.2,"Setosa" +5,3.4,1.6,.4,"Setosa" +5.2,3.5,1.5,.2,"Setosa" +5.2,3.4,1.4,.2,"Setosa" +4.7,3.2,1.6,.2,"Setosa" +4.8,3.1,1.6,.2,"Setosa" +5.4,3.4,1.5,.4,"Setosa" +5.2,4.1,1.5,.1,"Setosa" +5.5,4.2,1.4,.2,"Setosa" +4.9,3.1,1.5,.2,"Setosa" +5,3.2,1.2,.2,"Setosa" +5.5,3.5,1.3,.2,"Setosa" +4.9,3.6,1.4,.1,"Setosa" +4.4,3,1.3,.2,"Setosa" +5.1,3.4,1.5,.2,"Setosa" +5,3.5,1.3,.3,"Setosa" +4.5,2.3,1.3,.3,"Setosa" +4.4,3.2,1.3,.2,"Setosa" +5,3.5,1.6,.6,"Setosa" +5.1,3.8,1.9,.4,"Setosa" +4.8,3,1.4,.3,"Setosa" +5.1,3.8,1.6,.2,"Setosa" +4.6,3.2,1.4,.2,"Setosa" +5.3,3.7,1.5,.2,"Setosa" +5,3.3,1.4,.2,"Setosa" +7,3.2,4.7,1.4,"Versicolor" +6.4,3.2,4.5,1.5,"Versicolor" +6.9,3.1,4.9,1.5,"Versicolor" +5.5,2.3,4,1.3,"Versicolor" +6.5,2.8,4.6,1.5,"Versicolor" +5.7,2.8,4.5,1.3,"Versicolor" +6.3,3.3,4.7,1.6,"Versicolor" +4.9,2.4,3.3,1,"Versicolor" +6.6,2.9,4.6,1.3,"Versicolor" +5.2,2.7,3.9,1.4,"Versicolor" +5,2,3.5,1,"Versicolor" +5.9,3,4.2,1.5,"Versicolor" +6,2.2,4,1,"Versicolor" +6.1,2.9,4.7,1.4,"Versicolor" +5.6,2.9,3.6,1.3,"Versicolor" +6.7,3.1,4.4,1.4,"Versicolor" +5.6,3,4.5,1.5,"Versicolor" +5.8,2.7,4.1,1,"Versicolor" +6.2,2.2,4.5,1.5,"Versicolor" +5.6,2.5,3.9,1.1,"Versicolor" +5.9,3.2,4.8,1.8,"Versicolor" +6.1,2.8,4,1.3,"Versicolor" +6.3,2.5,4.9,1.5,"Versicolor" +6.1,2.8,4.7,1.2,"Versicolor" +6.4,2.9,4.3,1.3,"Versicolor" +6.6,3,4.4,1.4,"Versicolor" +6.8,2.8,4.8,1.4,"Versicolor" +6.7,3,5,1.7,"Versicolor" +6,2.9,4.5,1.5,"Versicolor" +5.7,2.6,3.5,1,"Versicolor" +5.5,2.4,3.8,1.1,"Versicolor" +5.5,2.4,3.7,1,"Versicolor" +5.8,2.7,3.9,1.2,"Versicolor" +6,2.7,5.1,1.6,"Versicolor" +5.4,3,4.5,1.5,"Versicolor" +6,3.4,4.5,1.6,"Versicolor" +6.7,3.1,4.7,1.5,"Versicolor" +6.3,2.3,4.4,1.3,"Versicolor" +5.6,3,4.1,1.3,"Versicolor" +5.5,2.5,4,1.3,"Versicolor" +5.5,2.6,4.4,1.2,"Versicolor" +6.1,3,4.6,1.4,"Versicolor" +5.8,2.6,4,1.2,"Versicolor" +5,2.3,3.3,1,"Versicolor" +5.6,2.7,4.2,1.3,"Versicolor" +5.7,3,4.2,1.2,"Versicolor" +5.7,2.9,4.2,1.3,"Versicolor" +6.2,2.9,4.3,1.3,"Versicolor" +5.1,2.5,3,1.1,"Versicolor" +5.7,2.8,4.1,1.3,"Versicolor" +6.3,3.3,6,2.5,"Virginica" +5.8,2.7,5.1,1.9,"Virginica" +7.1,3,5.9,2.1,"Virginica" +6.3,2.9,5.6,1.8,"Virginica" +6.5,3,5.8,2.2,"Virginica" +7.6,3,6.6,2.1,"Virginica" +4.9,2.5,4.5,1.7,"Virginica" +7.3,2.9,6.3,1.8,"Virginica" +6.7,2.5,5.8,1.8,"Virginica" +7.2,3.6,6.1,2.5,"Virginica" +6.5,3.2,5.1,2,"Virginica" +6.4,2.7,5.3,1.9,"Virginica" +6.8,3,5.5,2.1,"Virginica" +5.7,2.5,5,2,"Virginica" +5.8,2.8,5.1,2.4,"Virginica" +6.4,3.2,5.3,2.3,"Virginica" +6.5,3,5.5,1.8,"Virginica" +7.7,3.8,6.7,2.2,"Virginica" +7.7,2.6,6.9,2.3,"Virginica" +6,2.2,5,1.5,"Virginica" +6.9,3.2,5.7,2.3,"Virginica" +5.6,2.8,4.9,2,"Virginica" +7.7,2.8,6.7,2,"Virginica" +6.3,2.7,4.9,1.8,"Virginica" +6.7,3.3,5.7,2.1,"Virginica" +7.2,3.2,6,1.8,"Virginica" +6.2,2.8,4.8,1.8,"Virginica" +6.1,3,4.9,1.8,"Virginica" +6.4,2.8,5.6,2.1,"Virginica" +7.2,3,5.8,1.6,"Virginica" +7.4,2.8,6.1,1.9,"Virginica" +7.9,3.8,6.4,2,"Virginica" +6.4,2.8,5.6,2.2,"Virginica" +6.3,2.8,5.1,1.5,"Virginica" +6.1,2.6,5.6,1.4,"Virginica" +7.7,3,6.1,2.3,"Virginica" +6.3,3.4,5.6,2.4,"Virginica" +6.4,3.1,5.5,1.8,"Virginica" +6,3,4.8,1.8,"Virginica" +6.9,3.1,5.4,2.1,"Virginica" +6.7,3.1,5.6,2.4,"Virginica" +6.9,3.1,5.1,2.3,"Virginica" +5.8,2.7,5.1,1.9,"Virginica" +6.8,3.2,5.9,2.3,"Virginica" +6.7,3.3,5.7,2.5,"Virginica" +6.7,3,5.2,2.3,"Virginica" +6.3,2.5,5,1.9,"Virginica" +6.5,3,5.2,2,"Virginica" +6.2,3.4,5.4,2.3,"Virginica" +5.9,3,5.1,1.8,"Virginica" diff --git a/examples/external/pandas/pandas.pyproject b/examples/external/pandas/pandas.pyproject new file mode 100644 index 000000000..4731cf7d9 --- /dev/null +++ b/examples/external/pandas/pandas.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["dataframe_model.py"] +} diff --git a/examples/external/pandas/requirements.txt b/examples/external/pandas/requirements.txt new file mode 100644 index 000000000..fb6c7ed7e --- /dev/null +++ b/examples/external/pandas/requirements.txt @@ -0,0 +1 @@ +pandas diff --git a/examples/external/scikit/doc/scikit.png b/examples/external/scikit/doc/scikit.png Binary files differnew file mode 100644 index 000000000..930a1c7c9 --- /dev/null +++ b/examples/external/scikit/doc/scikit.png diff --git a/examples/external/scikit/doc/scikit.rst b/examples/external/scikit/doc/scikit.rst new file mode 100644 index 000000000..29ebbddbf --- /dev/null +++ b/examples/external/scikit/doc/scikit.rst @@ -0,0 +1,9 @@ +Scikit Image Example +==================== + +A Python application that demonstrates how to use Scikit Image +to apply filters to images based on a Qt Widgets. + +.. image:: scikit.png + :width: 400 + :alt: Scikit Image Screenshot diff --git a/examples/external/scikit/requirements.txt b/examples/external/scikit/requirements.txt new file mode 100644 index 000000000..391ca2f08 --- /dev/null +++ b/examples/external/scikit/requirements.txt @@ -0,0 +1 @@ +scikit-image diff --git a/examples/external/scikit/scikit.pyproject b/examples/external/scikit/scikit.pyproject new file mode 100644 index 000000000..5026c86e3 --- /dev/null +++ b/examples/external/scikit/scikit.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["staining_colors_separation.py"] +} diff --git a/examples/external/scikit/staining_colors_separation.py b/examples/external/scikit/staining_colors_separation.py new file mode 100644 index 000000000..94fdc3bdc --- /dev/null +++ b/examples/external/scikit/staining_colors_separation.py @@ -0,0 +1,147 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys + +import numpy as np +from matplotlib.backends.backend_qt5agg import FigureCanvas +from matplotlib.colors import LinearSegmentedColormap +from matplotlib.figure import Figure +from PySide6.QtCore import Qt, Slot +from PySide6.QtGui import QAction, QKeySequence +from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, + QMainWindow, QPushButton, QSizePolicy, + QVBoxLayout, QWidget) +from skimage import data +from skimage.color import rgb2hed +from skimage.exposure import rescale_intensity + + +class ApplicationWindow(QMainWindow): + """ + Example based on the example by 'scikit-image' gallery: + "Immunohistochemical staining colors separation" + https://scikit-image.org/docs/stable/auto_examples/color_exposure/plot_ihc_color_separation.html + """ + + def __init__(self, parent=None): + QMainWindow.__init__(self, parent) + self._main = QWidget() + self.setCentralWidget(self._main) + + # Main menu bar + self.menu = self.menuBar() + self.menu_file = self.menu.addMenu("File") + exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821 + self.menu_file.addAction(exit) + + self.menu_about = self.menu.addMenu("&About") + about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), + triggered=qApp.aboutQt) # noqa: F821 + self.menu_about.addAction(about) + + # Create an artificial color close to the original one + self.ihc_rgb = data.immunohistochemistry() + self.ihc_hed = rgb2hed(self.ihc_rgb) + + main_layout = QVBoxLayout(self._main) + plot_layout = QHBoxLayout() + button_layout = QHBoxLayout() + label_layout = QHBoxLayout() + + self.canvas1 = FigureCanvas(Figure(figsize=(5, 5))) + self.canvas2 = FigureCanvas(Figure(figsize=(5, 5))) + + self._ax1 = self.canvas1.figure.subplots() + self._ax2 = self.canvas2.figure.subplots() + + self._ax1.imshow(self.ihc_rgb) + + plot_layout.addWidget(self.canvas1) + plot_layout.addWidget(self.canvas2) + + self.button1 = QPushButton("Hematoxylin") + self.button2 = QPushButton("Eosin") + self.button3 = QPushButton("DAB") + self.button4 = QPushButton("Fluorescence") + + self.button1.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + self.button2.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + self.button3.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + self.button4.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + + self.button1.clicked.connect(self.plot_hematoxylin) + self.button2.clicked.connect(self.plot_eosin) + self.button3.clicked.connect(self.plot_dab) + self.button4.clicked.connect(self.plot_final) + + self.label1 = QLabel("Original", alignment=Qt.AlignCenter) + self.label2 = QLabel("", alignment=Qt.AlignCenter) + + font = self.label1.font() + font.setPointSize(16) + self.label1.setFont(font) + self.label2.setFont(font) + + label_layout.addWidget(self.label1) + label_layout.addWidget(self.label2) + + button_layout.addWidget(self.button1) + button_layout.addWidget(self.button2) + button_layout.addWidget(self.button3) + button_layout.addWidget(self.button4) + + main_layout.addLayout(label_layout, 2) + main_layout.addLayout(plot_layout, 88) + main_layout.addLayout(button_layout, 10) + + # Default image + self.plot_hematoxylin() + + def set_buttons_state(self, states): + self.button1.setEnabled(states[0]) + self.button2.setEnabled(states[1]) + self.button3.setEnabled(states[2]) + self.button4.setEnabled(states[3]) + + @Slot() + def plot_hematoxylin(self): + cmap_hema = LinearSegmentedColormap.from_list("mycmap", ["white", "navy"]) + self._ax2.imshow(self.ihc_hed[:, :, 0], cmap=cmap_hema) + self.canvas2.draw() + self.label2.setText("Hematoxylin") + self.set_buttons_state((False, True, True, True)) + + @Slot() + def plot_eosin(self): + cmap_eosin = LinearSegmentedColormap.from_list("mycmap", ["darkviolet", "white"]) + self._ax2.imshow(self.ihc_hed[:, :, 1], cmap=cmap_eosin) + self.canvas2.draw() + self.label2.setText("Eosin") + self.set_buttons_state((True, False, True, True)) + + @Slot() + def plot_dab(self): + cmap_dab = LinearSegmentedColormap.from_list("mycmap", ["white", "saddlebrown"]) + self._ax2.imshow(self.ihc_hed[:, :, 2], cmap=cmap_dab) + self.canvas2.draw() + self.label2.setText("DAB") + self.set_buttons_state((True, True, False, True)) + + @Slot() + def plot_final(self): + h = rescale_intensity(self.ihc_hed[:, :, 0], out_range=(0, 1)) + d = rescale_intensity(self.ihc_hed[:, :, 2], out_range=(0, 1)) + zdh = np.dstack((np.zeros_like(h), d, h)) + self._ax2.imshow(zdh) + self.canvas2.draw() + self.label2.setText("Stain separated image") + self.set_buttons_state((True, True, True, False)) + + +if __name__ == "__main__": + + app = QApplication(sys.argv) + w = ApplicationWindow() + w.show() + app.exec() |