Source code for SDF.GUI.browser.preview
from functools import wraps
from typing import Union, Optional
import matplotlib.pyplot as plt
import numpy as np
from PIL.Image import Image
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QCheckBox, QHBoxLayout, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from SDF.data_model import Dataset, SDFObject, ImageDataset, ArrayDataset2D, ArrayDataset1D
[docs]def update_ax(plot_func):
"""
Decorator for plot functions used in this widget. It performs all the pre- and
post-plotting work so that the plot functions just need to update the axis `self.ax`
"""
@wraps(plot_func)
def wrapper(self: "PlotPreviewWidget", *args, **kwargs):
# clear axis
self.ax.clear()
# call the actual plot function
result = plot_func(self, *args, **kwargs)
# update axis
self.ax.relim()
self.ax.autoscale()
self.canvas.draw()
return result
return wrapper
[docs]class PlotPreviewWidget(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.figure = plt.figure()
self.ax = self.figure.add_subplot(1, 1, 1)
self.canvas = FigureCanvas(self.figure)
self.init_ui()
[docs] def init_ui(self):
"""
Initializes all widgets (buttons, labels, etc.) displayed
on this widget.
"""
self.figure.subplots_adjust(top=0.96, bottom=0.08, left=0.08, right=0.96)
# navigation toolbar
toolbar = NavigationToolbar(self.canvas, self)
# layout
vbox = QVBoxLayout()
vbox.addWidget(toolbar)
vbox.addWidget(self.canvas)
self.setLayout(vbox)
[docs] @update_ax
def plot_image(self, img: Union[Image, np.ndarray]):
if isinstance(img, Image):
# plt.imshow displays grayscale PIL Images much brighter than np.ndarrays of it
img = np.asarray(img)
self.ax.imshow(img, cmap="gray")
[docs] @update_ax
def plot_array(self, data: np.ndarray, x_axis: int = None):
if data.ndim not in (1, 2):
raise ValueError(f"Cannot handle {data.ndim} dimensional arrays")
self.ax.set_aspect('auto')
# 1D
if data.ndim == 1:
self.ax.plot(data)
return
# 2D
y_indices = np.arange(data.shape[1])
if x_axis is None:
x = np.arange(data.shape[0])
else:
x = data[:, x_axis]
y_indices = y_indices[y_indices != x_axis]
if len(y_indices) > 10:
msg = f'Too many columns ({data.shape[1]}) to plot a preview'
self.ax.text(0.1, 0.5, msg, {'color': 'red', 'size': 16})
return
for y_index in y_indices:
self.ax.plot(x, data[:, y_index])
[docs]class DataPreviewWidget(QWidget):
def __init__(self):
super().__init__()
self.show_preview = True
self.sdf_obj: Optional[SDFObject] = None
self.plot_preview_widget = PlotPreviewWidget(self)
self.init_ui()
[docs] def init_ui(self):
"""Initializes all widgets (buttons, labels, etc.) displayed on this widget"""
self.setLayout(QVBoxLayout())
checkbox_data_preview = QCheckBox("Data preview")
checkbox_data_preview.toggle()
checkbox_data_preview.stateChanged.connect(self.toggle_show_preview)
hbox_checkbox = QHBoxLayout()
hbox_checkbox.addWidget(checkbox_data_preview)
hbox_checkbox.addStretch(1)
self.layout().addLayout(hbox_checkbox)
self.layout().addWidget(self.plot_preview_widget)
[docs] def display_sdf_object(self, obj: SDFObject) -> None:
"""
Function to update shwon information to match given sdf_object.
"""
self.sdf_obj = obj
if not self.show_preview:
return
if not isinstance(obj, Dataset):
self.plot_preview_widget.clear()
return
if isinstance(obj, ImageDataset):
self.plot_preview_widget.plot_image(obj.data)
elif isinstance(obj, (ArrayDataset1D, ArrayDataset2D)):
self.plot_preview_widget.plot_array(obj.data)
else:
self.plot_preview_widget.clear()
[docs] def toggle_show_preview(self, state: int) -> None:
if state == Qt.Checked:
self.show_preview = True
if self.sdf_obj is None:
return
self.display_sdf_object(self.sdf_obj)
else:
self.show_preview = False
self.plot_preview_widget.clear()