Examples — PlotPy 2.7 Manual (original) (raw)

The test launcher

A lot of examples are available in the plotpy.tests test module

from plotpy.tests import run run()

The two lines above execute the test launcher:

../_images/__init__.png

Curve plotting

Basic curve plotting

../_images/plot.png

Computations on curves

import numpy as np import scipy.integrate as spt from guidata.qthelpers import qt_app_context

from plotpy.builder import make from plotpy.tests import vistools as ptv

def test_computations(): """Test computations""" x = np.linspace(-10, 10, 1000) y = np.sin(np.sin(np.sin(x))) with qt_app_context(exec_loop=True): curve = make.curve(x, y, "ab", "b") range = make.range(-2, 2) disp0 = make.range_info_label( range, "BR", "x = %.1f ± %.1f cm", title="Range infos" )

    disp1 = make.computation(
        range, "BL", "trapz=%g", curve, lambda x, y: spt.trapezoid(y, x)
    )

    disp2 = make.computations(
        range,
        "TL",
        [
            (curve, "min=%.5f", lambda x, y: y.min()),
            (curve, "max=%.5f", lambda x, y: y.max()),
            (curve, "avg=%.5f", lambda x, y: y.mean()),
        ],
    )
    legend = make.legend("TR")
    _win = ptv.show_items(
        wintitle="Plot computations",
        items=[curve, range, disp0, disp1, disp2, legend],
        plot_type="curve",
    )

if name == "main": test_computations()

../_images/computations.png

Curve fitting

import numpy as np

from plotpy.widgets.fit import FitParam, guifit

def test_fit(): """Test the curve fitting tool""" x = np.linspace(-10, 10, 1000) y = np.cos(1.5 * x) + np.random.rand(x.shape[0]) * 0.2

def fit(x, params):
    a, b = params
    return np.cos(b * x) + a

a = FitParam("Offset", 0.7, -1.0, 1.0)
b = FitParam("Frequency", 1.2, 0.3, 3.0, logscale=True)
params = [a, b]
values = guifit(x, y, fit, params, xlabel="Time (s)", ylabel="Power (a.u.)")

print(values)
print([param.value for param in params])

if name == "main": test_fit()

../_images/fit.png

Image visualization

Image contrast adjustment

import os import os.path as osp

from guidata.env import execenv from guidata.qthelpers import qt_app_context

from plotpy.builder import make from plotpy.tests import get_path from plotpy.tests.data import gen_image1

def __create_dialog_with_contrast(item): """Create plot dialog with contrast panel

Args:
    Item: item to be added to the plot
"""
win = make.dialog(
    edit=False,
    toolbar=True,
    wintitle="Contrast test",
    show_contrast=True,
    type="image",
    size=(600, 600),
)
plot = win.get_plot()
plot.add_item(item)
plot.set_active_item(item)
item.unselect()
win.show()
return win

def test_contrast1(): """Contrast test 1""" with qt_app_context(exec_loop=True): item = make.image(filename=get_path("brain.png"), colormap="gray") win = __create_dialog_with_contrast(item) fname = "contrast.png" try: win.get_plot().save_widget(fname) except IOError: # Skipping this part of the test # because user has no write permission on current directory pass if execenv.unattended and osp.isfile(fname): os.unlink(fname)

def test_contrast2(): """Contrast test 2

Test if level histogram is really removed when the associated image is removed from
the plot (the validation is not automatic)
"""
with qt_app_context(exec_loop=True):
    item1 = make.image(filename=get_path("brain.png"), colormap="gray")
    win = __create_dialog_with_contrast(item1)
    plot = win.get_plot()
    plot.del_item(item1)
    item2 = make.image(gen_image1())
    plot.add_item(item2)
    plot.set_active_item(item2)

if name == "main": # test_contrast1() test_contrast2()

../_images/contrast.png

Image cross-sections

import numpy as np from guidata.qthelpers import qt_app_context

from plotpy.builder import make from plotpy.tests import get_path

def create_window(): win = make.dialog( edit=False, toolbar=True, wintitle="Cross sections test", show_xsection=True, show_ysection=True, type="image", size=(640, 600), ) return win

def test_cross_section(): """Test cross section""" with qt_app_context(exec_loop=True): filename = get_path("brain.png") win = create_window() win.show() image = make.image(filename=filename, colormap="bone") data2 = np.array(image.data.T[200:], copy=True) image2 = make.image(data2, title="Modified", alpha_function="linear") plot = win.manager.get_plot() plot.add_item(image) plot.add_item(image2, z=1)

if name == "main": test_cross_section()

../_images/cross_section.png

Transformable images

Affine transforms example on 3000x3000 images (real-time transforms):

from future import annotations

import os

import numpy as np import pytest from guidata.env import execenv from guidata.qthelpers import qt_app_context from qtpy import QtCore as QC from qtpy import QtGui as QG

from plotpy import io from plotpy.builder import make from plotpy.constants import LUTAlpha from plotpy.items import TrImageItem, assemble_imageitems from plotpy.tests import vistools as ptv from plotpy.tests.data import gen_image4

DEFAULT_CHARS = "".join([chr(c) for c in range(32, 256)])

def get_font_array(sz: int, chars: str = DEFAULT_CHARS) -> np.ndarray | None: """Return array of font characters

Args:
    sz: Font size
    chars: Characters to include (default: all printable characters)

Returns:
    Array of font characters
"""
font = QG.QFont()
font.setFixedPitch(True)
font.setPixelSize(sz)
font.setStyleStrategy(QG.QFont.NoAntialias)
dummy = QG.QImage(10, 10, QG.QImage.Format_ARGB32)
pnt = QG.QPainter(dummy)
pnt.setFont(font)
metric = pnt.fontMetrics()
rct = metric.boundingRect(chars)
pnt.end()
h, w = rct.height(), rct.width()
img = QG.QImage(w, h, QG.QImage.Format_ARGB32)
paint = QG.QPainter()
paint.begin(img)
paint.setFont(font)
paint.setBrush(QG.QColor(255, 255, 255))
paint.setPen(QG.QColor(255, 255, 255))
paint.drawRect(0, 0, w + 1, h + 1)
paint.setPen(QG.QColor(0, 0, 0))
paint.setBrush(QG.QColor(0, 0, 0))
paint.drawText(0, paint.fontMetrics().ascent(), chars)
paint.end()
try:
    data = img.bits().asstring(h * w * 4)
except AttributeError:
    data = img.bits()
npy: np.ndarray = np.frombuffer(data, np.uint8)
return npy.reshape(h, w, 4)[:, :, 0]

def write_text_on_array( data: np.ndarray, x: int, y: int, sz: int, txt: str, range: tuple[int, int] | None = None, ) -> None: """Write text in image (in-place)

Args:
    data: Image data
    x: X-coordinate of top-left corner
    y: Y-coordinate of top-left corner
    sz: Font size
    txt: Text to write
    range: Range of values to map to 0-255 (default: None)
"""
arr = get_font_array(sz, txt)
if arr is None:
    return
if range is None:
    m, M = data.min(), data.max()
else:
    m, M = range
z = (float(M) - float(m)) * np.array(arr, float) / 255.0 + m
arr = np.array(z, data.dtype)
dy, dx = arr.shape
data[y : y + dy, x : x + dx] = arr

def make_items(N: int) -> list[TrImageItem]: """Make test TrImageItem items

Args:
    N: Image size (N x N)

Returns:
    List of image items
"""
data = gen_image4(N, N)
m = data.min()
M = data.max()
items = [make.trimage(data, alpha_function=LUTAlpha.LINEAR, colormap="jet")]
for dtype in (np.uint8, np.uint16, np.int8, np.int16):
    info = np.iinfo(dtype().dtype)  # pylint: disable=no-value-for-parameter
    s = float((info.max - info.min))
    a1 = s * (data - m) / (M - m)
    img = np.array(a1 + info.min, dtype)
    write_text_on_array(img, 0, 0, int(N / 15.0), dtype.__name__)
    items.append(make.trimage(img, colormap="jet"))
nc = int(np.sqrt(len(items)) + 1.0)
maxy, x, y = 0, 0, 0
w = None
for index, item in enumerate(items):
    h = item.boundingRect().height()
    if index % nc == 0:
        x = 0
        y += maxy
        maxy = h
    else:
        x += w
        maxy = max(maxy, h)
    w = item.boundingRect().width()
    item.set_transform(x, y, 0.0)
    # item.set_selectable(False)
return items

def save_image(name: str, data: np.ndarray) -> None: """Save image to file

Args:
    name: Base name of file
    data: Image data
"""
for fname in (name + ".u16.tif", name + ".u8.png"):
    if os.path.exists(fname):
        os.remove(fname)
size = int(data.nbytes / 1024.0)
print(f"Saving image: {data.shape[0]} x {data.shape[1]} ({size} KB):")
print(" --> uint16")
io.imwrite(name + ".u16.tif", data, dtype=np.uint16, max_range=True)
print(" --> uint8")
io.imwrite(name + ".u8.png", data, dtype=np.uint8, max_range=True)

def get_bbox(items: list[TrImageItem]) -> QC.QRectF: """Get bounding box of items

Args:
    items: List of image items

Returns:
    Bounding box of items
"""
rectf = QC.QRectF()
for item in items:
    rectf = rectf.united(item.boundingRect())
return rectf

def build_image(items: list[TrImageItem]) -> None: """Build image from items

Args:
    items: List of image items
"""
r = get_bbox(items)
_x, _y, w, h = r.getRect()
print("-" * 80)
print(f"Assemble test1: {int(w)} x {int(h)}")
dest = assemble_imageitems(items, r, w, h)
if not execenv.unattended:
    save_image("test1", dest)
print("-" * 80)
print(f"Assemble test1: {int(w/4)} x {int(h/4)}")
dest = assemble_imageitems(items, r, w / 4, h / 4)
if not execenv.unattended:
    save_image("test2", dest)
print("-" * 80)

@pytest.mark.parametrize("N", [500]) @pytest.mark.parametrize("assemble_images", [False, True]) def test_transform(N: int, assemble_images: bool) -> None: """Test image transforms

Args:
    N: Image size (N x N)
    assemble_images: If True, assemble images (default: False)
"""
with qt_app_context(exec_loop=True):
    items = make_items(N)
    _win = ptv.show_items(
        items,
        wintitle="Transform test ({}x{} images)".format(N, N),
        plot_type="image",
        show_itemlist=True,
        winsize=(1000, 600),
    )
if assemble_images:
    build_image(items)

if name == "main": test_transform(N=500, assemble_images=True)

../_images/transform.png

Image rectangular filter

../_images/imagefilter.png

Histograms

2-D histogram

from guidata.qthelpers import qt_app_context from numpy import array, concatenate, dot, random

from plotpy.builder import make from plotpy.config import _

def hist2d_func(X, Y, Z): with qt_app_context(exec_loop=True): win = make.dialog( edit=True, toolbar=True, wintitle="2-D Histogram X0=(0,1), X1=(-1,-1)", type="image", ) hist2d = make.histogram2D(X, Y, 200, 200, Z=Z, computation=2) curve = make.curve( X[::50], Y[::50], linestyle="", marker="+", title=_("Markers") ) plot = win.manager.get_plot() plot.set_aspect_ratio(lock=False) plot.set_antialiasing(False) plot.add_item(hist2d) plot.add_item(curve) plot.set_item_visible(curve, False) win.show()

def hist2d(X, Y): with qt_app_context(exec_loop=True): win = make.dialog( edit=True, toolbar=True, wintitle="2-D Histogram X0=(0,1), X1=(-1,-1)", type="image", ) hist2d = make.histogram2D(X, Y, 200, 200) curve = make.curve( X[::50], Y[::50], linestyle="", marker="+", title=_("Markers") ) plot = win.manager.get_plot() plot.set_aspect_ratio(lock=False) plot.set_antialiasing(False) plot.add_item(hist2d) plot.add_item(curve) plot.set_item_visible(curve, False) win.show()

def test_hist_2d(): N = 150000 m = array([[1.0, 0.2], [-0.2, 3.0]]) X1 = random.normal(0, 0.3, size=(N, 2)) X2 = random.normal(0, 0.3, size=(N, 2)) X = concatenate((X1 + [0, 1.0], dot(X2, m) + [-1, -1.0])) hist2d(X[:, 0], X[:, 1])

def test_hist_2d_func(): N = 150000 m = array([[1.0, 0.2], [-0.2, 3.0]]) X1 = random.normal(0, 0.3, size=(N, 2)) X2 = random.normal(0, 0.3, size=(N, 2)) X = concatenate((X1 + [0, 1.0], dot(X2, m) + [-1, -1.0])) hist2d_func(X[:, 0], X[:, 1], X[:, 0] + X[:, 1])

if name == "main": test_hist_2d() test_hist_2d_func()

../_images/hist2d.png

Other examples

Dot Array Demo

from future import annotations

from typing import TYPE_CHECKING

import guidata.dataset as gds import guidata.dataset.qtwidgets as gdq import numpy as np from guidata.configtools import get_image_file_path from guidata.qthelpers import qt_app_context from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtWidgets as QW

import plotpy.config # Loading icons # noqa: F401 from plotpy.interfaces import IImageItemType from plotpy.items import RawImageItem from plotpy.items.curve.errorbar import vmap from plotpy.plot import PlotDialog, PlotOptions from plotpy.styles import RawImageParam from plotpy.tools import CopyToClipboardTool, HelpTool, PrintTool, SaveAsTool

if TYPE_CHECKING: from plotpy.interfaces import IItemType

class DotArrayParam(gds.DataSet): """Dot array"""

def _update_cb(self, *args):
    """Update callback, to be overriden"""
    pass

g1 = gds.BeginGroup("Size of the area")
dim_h = gds.FloatItem("Width", default=20, min=0, unit="mm")
dim_v = gds.FloatItem("Height", default=20, min=0, unit="mm")
_g1 = gds.EndGroup("Size of the area")

g2 = gds.BeginGroup("Grid pattern properties")
step_x = gds.FloatItem("Step in X-axis", default=1, min=1, unit="mm")
step_y = gds.FloatItem("Step in Y-axis", default=1, min=1, unit="mm")
size = gds.FloatItem("Dot size", default=0.2, min=0, max=2, slider=True, unit="mm")
color = gds.ColorItem("Dot color", default="red")
_g2 = gds.EndGroup("Grid pattern properties")

def update_item(self, obj):
    """Update item from parameters"""
    self._update_cb()

def update_param(self, obj):
    """Update parameters from object"""
    pass

class DotArrayRawImageParam(RawImageParam, DotArrayParam): pass

class DotArrayItem(RawImageItem): """Dot array item"""

def __init__(self, param=None):
    super().__init__(np.zeros((1, 1)), param)
    self.update_border()

def boundingRect(self):
    """Reimplemented to return the bounding rectangle of the item"""
    param = self.param
    if param is not None:
        return QC.QRectF(
            QC.QPointF(-0.5 * param.size, -0.5 * param.size),
            QC.QPointF(
                param.dim_h + 0.5 * param.size, param.dim_v + 0.5 * param.size
            ),
        )

def types(self) -> tuple[type[IItemType], ...]:
    """Returns a group or category for this item.
    This should be a tuple of class objects inheriting from IItemType

    Returns:
        tuple: Tuple of class objects inheriting from IItemType
    """
    return (IImageItemType,)

def draw_image(self, painter, canvasRect, srcRect, dstRect, xMap, yMap):
    """Draw image"""
    if self.warn_if_non_linear_scale(painter, canvasRect):
        return
    painter.setRenderHint(QG.QPainter.Antialiasing, True)
    param = self.param
    xcoords = vmap(xMap, np.arange(0, param.dim_h + 1, param.step_x))
    ycoords = vmap(yMap, np.arange(0, param.dim_v + 1, param.step_y))
    rx = 0.5 * param.size * xMap.pDist() / xMap.sDist()
    ry = 0.5 * param.size * yMap.pDist() / yMap.sDist()
    color = QG.QColor(param.color)
    painter.setPen(QG.QPen(color))
    painter.setBrush(QG.QBrush(color))
    for xc in xcoords:
        for yc in ycoords:
            painter.drawEllipse(QC.QPointF(xc, yc), rx, ry)

class CustomHelpTool(HelpTool): """Custom help tool"""

def activate_command(self, plot, checked):
    """Activate command"""
    QW.QMessageBox.information(
        plot,
        "Help",
        """**to be customized**

Keyboard/mouse shortcuts:

class DotArrayDialog(PlotDialog): """Dot array dialog"""

def __init__(self):
    self.item = None
    self.stamp_gbox = None
    super().__init__(
        title="Dot array example",
        options=PlotOptions(title="Main plot", type="image"),
        toolbar=True,
        edit=True,
    )
    self.resize(900, 600)

def register_tools(self):
    """Register tools"""
    manager = self.plot_widget.manager
    manager.register_standard_tools()
    manager.add_separator_tool()
    manager.add_tool(SaveAsTool)
    manager.add_tool(CopyToClipboardTool)
    manager.add_tool(PrintTool)
    manager.add_tool(CustomHelpTool)
    manager.activate_default_tool()
    plot = manager.get_plot()
    plot.enableAxis(plot.yRight, False)
    plot.set_aspect_ratio(lock=True)

def populate_plot_layout(self):
    """Populate the plot layout

    Reimplements the method from PlotDialog"""
    self.add_widget(self.plot_widget, row=0, column=0, rowspan=3, columnspan=1)
    logo_path = get_image_file_path("plotpy.svg")
    logo = QW.QLabel()
    logo.setPixmap(QG.QPixmap(logo_path))
    logo.setAlignment(QC.Qt.AlignCenter)
    self.add_widget(logo, 1, 1)
    logo_txt = QW.QLabel("Powered by <b>plotpy</b>")
    logo_txt.setAlignment(QC.Qt.AlignHCenter | QC.Qt.AlignTop)
    self.add_widget(logo_txt, 2, 1)
    self.stamp_gbox = gdq.DataSetEditGroupBox("Dots", DotArrayParam)
    self.stamp_gbox.SIG_APPLY_BUTTON_CLICKED.connect(self.apply_params)
    self.add_widget(self.stamp_gbox, 0, 1)

def show_data(self, param):
    """Show data"""
    plot = self.plot_widget.plot
    if self.item is None:
        itemparam = DotArrayRawImageParam()
        gds.update_dataset(itemparam, param)
        param._update_cb = lambda: self.stamp_gbox.get()
        self.item = DotArrayItem(itemparam)
        plot.add_item(self.item)
    else:
        gds.update_dataset(self.item.param, param)
        self.item.update_border()
    plot.do_autoscale()

def apply_params(self):
    """Apply parameters"""
    param = self.stamp_gbox.dataset
    self.show_data(param)

def test_dot_array(): """Test dot array dialog""" with qt_app_context(exec_loop=True): dlg = DotArrayDialog() dlg.apply_params() dlg.show()

if name == "main": test_dot_array()

../_images/dotarraydemo.png

Image plot tools

from guidata.qthelpers import qt_app_context from qtpy import QtWidgets as QW

from plotpy.builder import make from plotpy.items import Marker from plotpy.tests import get_path from plotpy.tools import ( AnnotatedCircleTool, AnnotatedEllipseTool, AnnotatedObliqueRectangleTool, AnnotatedPointTool, AnnotatedPolygonTool, AnnotatedRectangleTool, AnnotatedSegmentTool, CircleTool, EllipseTool, HCursorTool, HRangeTool, LabelTool, MultiLineTool, ObliqueRectangleTool, PlaceAxesTool, PolygonTool, RectangleTool, SegmentTool, VCursorTool, XCursorTool, )

TOOLBAR_ID = "toolbar2"

def create_window(): win = make.dialog( edit=False, toolbar=True, wintitle="All image and plot tools test", type="image", size=(800, 600), ) toolbar2 = QW.QToolBar() win.layout().addWidget(toolbar2) win.manager.add_toolbar(toolbar2, TOOLBAR_ID)

for toolklass in (
    LabelTool,
    HRangeTool,
    VCursorTool,
    HCursorTool,
    XCursorTool,
    SegmentTool,
    RectangleTool,
    ObliqueRectangleTool,
    CircleTool,
    EllipseTool,
    MultiLineTool,
    PolygonTool,
    PlaceAxesTool,
    AnnotatedRectangleTool,
    AnnotatedObliqueRectangleTool,
    AnnotatedCircleTool,
    AnnotatedEllipseTool,
    AnnotatedSegmentTool,
    AnnotatedPointTool,
    AnnotatedPolygonTool,
):
    win.manager.add_tool(toolklass, toolbar_id=TOOLBAR_ID)
return win

def test_image_plot_tools(): """Test""" with qt_app_context(exec_loop=True): filename = get_path("brain.png") win = create_window() win.show() image = make.image(filename=filename, colormap="bone") plot = win.manager.get_plot() plot.add_item(image) title = "toto" marker1 = Marker(label_cb=lambda x, y: f"{title}x = {x:g}
y = {y:g}") plot.add_item(marker1) marker2 = Marker(label_cb=lambda x, y: f"{title}x = {x:g}
y = {y:g}") plot.add_item(marker2)

if name == "main": test_image_plot_tools()

../_images/image_plot_tools.png

Real-time Mandelbrot plotting

import numpy as np from guidata.qthelpers import qt_app_context from qtpy import QtCore as QC

from plotpy.builder import make from plotpy.config import _ from plotpy.items import RawImageItem from plotpy.mandelbrot import mandelbrot from plotpy.tools import ToggleTool

class FullScale(ToggleTool): def init(self, parent, image): super().init(parent, _("MAX resolution"), None) self.image = image self.minprec = image.IMAX self.maxprec = 5 * image.IMAX

def activate_command(self, plot, checked):
    if self.image.IMAX == self.minprec:
        self.image.IMAX = self.maxprec
    else:
        self.image.IMAX = self.minprec
    self.image.set_lut_range([0, self.image.IMAX])
    plot.replot()

def update_status(self, plot):
    self.action.setChecked(self.image.IMAX == self.maxprec)

class MandelItem(RawImageItem): def init(self, xmin, xmax, ymin, ymax): super().init(np.zeros((1, 1), np.uint8)) self.bounds = QC.QRectF(QC.QPointF(xmin, ymin), QC.QPointF(xmax, ymax)) self.update_border() self.IMAX = 80 self.set_lut_range([0, self.IMAX])

# ---- QwtPlotItem API ------------------------------------------------------
def draw_image(self, painter, canvasRect, srcRect, dstRect, xMap, yMap):
    if self.warn_if_non_linear_scale(painter, canvasRect):
        return
    x1, y1, x2, y2 = canvasRect.toAlignedRect().getCoords()
    i1, j1, i2, j2 = srcRect

    NX = x2 - x1
    NY = y2 - y1
    if self.data.shape != (NX, NY):
        self.data = np.zeros((NY, NX), np.int16)
    mandelbrot(i1, j1, i2, j2, self.data, self.IMAX)

    srcRect = (0, 0, NX, NY)
    x1, y1, x2, y2 = canvasRect.toAlignedRect().getCoords()
    RawImageItem.draw_image(
        self, painter, canvasRect, srcRect, (x1, y1, x2, y2), xMap, yMap
    )

def create_mandelbrot_window(): """Create a Mandelbrot set window""" win = make.window( toolbar=True, wintitle="Mandelbrot", yreverse=False, type="image", ) mandel = MandelItem(-1.5, 0.5, -1.0, 1.0) fstool = win.manager.add_tool(FullScale, mandel) plot = win.get_plot() plot.set_aspect_ratio(lock=False) plot.add_item(mandel) return win, mandel, fstool

def test_mandel(): """Test Mandelbrot set window""" with qt_app_context(exec_loop=True): win, _mandel, _fstool = create_mandelbrot_window() win.show()

if name == "main": test_mandel()

../_images/mandelbrot.png

Simple application

import sys

import numpy as np from guidata.configtools import get_icon from guidata.dataset import ( ChoiceItem, DataSet, FloatArrayItem, GetAttrProp, IntItem, StringItem, update_dataset, ) from guidata.dataset.qtwidgets import DataSetEditGroupBox from guidata.qthelpers import ( add_actions, create_action, get_std_icon, qt_app_context, win32_fix_title_bar_background, ) from qtpy import QtCore as QC from qtpy import QtWidgets as QW

from plotpy import io from plotpy.builder import make from plotpy.config import _ from plotpy.plot import PlotOptions, PlotWidget from plotpy.tests import get_path from plotpy.widgets import about

class ImageParam(DataSet): hide_data = False hide_size = True title = StringItem(("Title"), default=("Untitled")) data = FloatArrayItem(_("Data")).set_prop("display", hide=GetAttrProp("_hide_data")) width = IntItem( ("Width"), help=("Image width (pixels)"), min=1, default=100 ).set_prop("display", hide=GetAttrProp("_hide_size")) height = IntItem( ("Height"), help=("Image height (pixels)"), min=1, default=100 ).set_prop("display", hide=GetAttrProp("_hide_size"))

class ImageParamNew(ImageParam): _hide_data = True hide_size = False type = ChoiceItem(("Type"), (("rand", _("random")), ("zeros", _("zeros"))))

class ImageListWithProperties(QW.QSplitter): def init(self, parent): QW.QSplitter.init(self, parent) self.imagelist = QW.QListWidget(self) self.addWidget(self.imagelist) self.properties = DataSetEditGroupBox(_("Properties"), ImageParam) self.properties.setEnabled(False) self.addWidget(self.properties)

class CentralWidget(QW.QSplitter): def init(self, parent, toolbar): QW.QSplitter.init(self, parent) self.setContentsMargins(10, 10, 10, 10) self.setOrientation(QC.Qt.Vertical)

    imagelistwithproperties = ImageListWithProperties(self)
    self.addWidget(imagelistwithproperties)
    self.imagelist = imagelistwithproperties.imagelist
    self.imagelist.currentRowChanged.connect(self.current_item_changed)
    self.imagelist.itemSelectionChanged.connect(self.selection_changed)
    self.properties = imagelistwithproperties.properties
    self.properties.SIG_APPLY_BUTTON_CLICKED.connect(self.properties_changed)

    self.plot_widget = PlotWidget(
        self,
        options=PlotOptions(type="image", show_contrast=True),
        auto_tools=False,
    )
    self.plot_widget.plot.SIG_LUT_CHANGED.connect(self.lut_range_changed)
    self.item = None  # image item

    self.plot_widget.manager.add_toolbar(toolbar, "default")
    self.plot_widget.register_tools()

    self.addWidget(self.plot_widget)

    self.images = []  # List of ImageParam instances
    self.lut_ranges = []  # List of LUT ranges

    self.setStretchFactor(0, 0)
    self.setStretchFactor(1, 1)
    self.setHandleWidth(10)
    self.setSizes([1, 2])

def refresh_list(self):
    """Refresh image list"""
    self.imagelist.clear()
    self.imagelist.addItems([image.title for image in self.images])

def selection_changed(self):
    """Image list: selection changed"""
    row = self.imagelist.currentRow()
    self.properties.setDisabled(row == -1)

def current_item_changed(self, row):
    """Image list: current image changed"""
    if row == -1:
        return
    image, lut_range = self.images[row], self.lut_ranges[row]
    self.show_data(image.data, lut_range)
    update_dataset(self.properties.dataset, image)
    self.properties.get()

def lut_range_changed(self):
    """LUT range changed"""
    row = self.imagelist.currentRow()
    self.lut_ranges[row] = self.item.get_lut_range()

def show_data(self, data, lut_range=None):
    """Show image data"""
    plot = self.plot_widget.plot
    if self.item is not None:
        self.item.set_data(data)
        if lut_range is None:
            lut_range = self.item.get_lut_range()
        self.plot_widget.manager.set_contrast_range(*lut_range)
        self.plot_widget.manager.update_cross_sections()
    else:
        self.item = make.image(data, interpolation="nearest")
        plot.add_item(self.item, z=0)
    plot.select_item(self.item)
    plot.do_autoscale()
    plot.replot()

def properties_changed(self):
    """The properties 'Apply' button was clicked: updating image"""
    row = self.imagelist.currentRow()
    image = self.images[row]
    update_dataset(image, self.properties.dataset)
    self.refresh_list()
    self.show_data(image.data)

def add_image(self, image):
    """Add image"""
    self.images.append(image)
    self.lut_ranges.append(None)
    self.refresh_list()
    self.imagelist.setCurrentRow(len(self.images) - 1)
    plot = self.plot_widget.plot
    plot.do_autoscale()

def add_image_from_file(self, filename):
    """Add image from file"""
    image = ImageParam()
    image.title = str(filename)
    image.data = io.imread(filename, to_grayscale=True)
    image.height, image.width = image.data.shape
    self.add_image(image)

def remove_image(self, index=None):
    """Remove image"""
    if index is None:
        index = self.imagelist.currentRow()
    del self.images[index]
    del self.lut_ranges[index]
    self.refresh_list()
    if self.imagelist.count() > 0:
        self.imagelist.setCurrentRow(0)
    else:
        self.item = None
        self.plot_widget.plot.del_all_items()
        self.plot_widget.plot.replot()

class MainWindow(QW.QMainWindow): """Main Window"""

def __init__(self):
    super().__init__()
    win32_fix_title_bar_background(self)
    self.setup()

def setup(self):
    """Setup window parameters"""
    self.setWindowIcon(get_icon("python.png"))
    self.setWindowTitle(_("Application example"))
    self.resize(QC.QSize(600, 800))

    # Welcome message in statusbar:
    status = self.statusBar()
    status.showMessage(_("Welcome to plotpy application example!"), 5000)

    # Set central widget:
    main_toolbar = self.addToolBar("Main")
    toolbar = self.addToolBar("Image")
    self.mainwidget = CentralWidget(self, toolbar)
    self.setCentralWidget(self.mainwidget)

    # File menu
    file_menu = self.menuBar().addMenu(_("File"))
    new_action = create_action(
        self,
        _("New..."),
        shortcut="Ctrl+N",
        icon=get_icon("filenew.png"),
        tip=_("Create a new image"),
        triggered=self.new_image,
    )
    open_action = create_action(
        self,
        _("Open..."),
        shortcut="Ctrl+O",
        icon=get_icon("fileopen.png"),
        tip=_("Open an image"),
        triggered=self.open_image,
    )
    quit_action = create_action(
        self,
        _("Quit"),
        shortcut="Ctrl+Q",
        icon=get_std_icon("DialogCloseButton"),
        tip=_("Quit application"),
        triggered=self.close,
    )
    add_actions(file_menu, (new_action, open_action, None, quit_action))

    # Edit menu
    edit_menu = self.menuBar().addMenu(_("Edit"))
    del_action = create_action(
        self,
        _("Delete"),
        shortcut="Del",
        icon=get_icon("editdelete.png"),
        tip=_("Delete selected image"),
        triggered=self.mainwidget.remove_image,
    )
    add_actions(edit_menu, (del_action,))

    # Help menu
    help_menu = self.menuBar().addMenu("?")
    about_action = create_action(
        self,
        _("About %s...") % "PlotPy",
        icon=get_std_icon("MessageBoxInformation"),
        triggered=about.show_about_dialog,
    )
    add_actions(help_menu, (about_action,))

    add_actions(main_toolbar, (new_action, open_action))

# ------I/O
def new_image(self, imagenew=None):
    """Create a new image"""
    if imagenew is None:
        imagenew = ImageParamNew(title=_("Create a new image"))
        if not imagenew.edit(self):
            return
    image = ImageParam()
    image.title = imagenew.title
    if imagenew.type == "zeros":
        image.data = np.zeros((imagenew.width, imagenew.height))
    elif imagenew.type == "rand":
        image.data = np.random.randn(imagenew.width, imagenew.height)
    self.mainwidget.add_image(image)

def open_image(self, filename=None):
    """Open image file"""
    if filename is None:
        saved_in, saved_out, saved_err = sys.stdin, sys.stdout, sys.stderr
        sys.stdout = None
        filename, _filter = QW.QFileDialog.getOpenFileName(
            self,
            _("Open"),
            "",
            io.iohandler.get_filters("load"),
            "",
            options=QW.QFileDialog.ShowDirsOnly,
        )
        sys.stdin, sys.stdout, sys.stderr = saved_in, saved_out, saved_err
    if filename:
        self.mainwidget.add_image_from_file(filename)

def test_simple_window(): """Test simple window""" with qt_app_context(exec_loop=True): window = MainWindow() window.show() window.new_image(imagenew=ImageParamNew.create(type="rand")) window.open_image(filename=get_path("brain.png"))

if name == "main": test_simple_window()

../_images/simple_window.png