# -*- coding: utf-8 -*-
#
# Licensed under the terms of the BSD 3-Clause
# (see plotpy/LICENSE for details)
"""
Rotate/crop dialog
------------------
The `rotatecrop` module provides a dialog box providing essential GUI elements
for rotating (arbitrary angle) and cropping an image:
* :py:class:`.widgets.rotatecrop.RotateCropDialog`: dialog box
* :py:class:`.widgets.rotatecrop.RotateCropWidget`: equivalent widget
Reference
~~~~~~~~~
.. autoclass:: RotateCropDialog
:members:
.. autoclass:: RotateCropWidget
:members:
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from guidata.qthelpers import win32_fix_title_bar_background
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy.QtWidgets import QWidget # Helping out python_qt_documentation
from plotpy.builder import make
from plotpy.config import _
from plotpy.items import get_image_in_shape
from plotpy.widgets import basetransform
if TYPE_CHECKING:
import numpy as np
from plotpy.items import AnnotatedRectangle, TrImageItem
from plotpy.plot import PlotOptions
from plotpy.plot.manager import PlotManager
class RotateCropTransform(basetransform.BaseTransform):
"""Rotate & Crop utils class, to be mixed with a class providing the
get_plot method, like PlotDialog or RotateCropWidget (see below)
Args:
parent: Parent widget
manager: Plot manager
"""
def __init__(self, parent: RotateCropWidget, manager: PlotManager) -> None:
super().__init__(parent, manager)
self.crop_rect: AnnotatedRectangle = None
self.manager = manager
# ------BaseTransformUtils API----------------------------------------------
def set_item(self, item: TrImageItem) -> None:
"""Set associated item
Args:
item: Associated item
"""
super().set_item(item)
crect: AnnotatedRectangle = make.annotated_rectangle(
0, 0, 1, 1, _("Cropping rectangle")
)
self.crop_rect = crect
crect.annotationparam.format = "%.1f cm"
plot = self.manager.get_plot()
plot.add_item(crect)
plot.set_active_item(crect)
x0, y0, x1, y1 = self.item.get_crop_coordinates()
crect.set_rect(x0, y0, x1, y1)
plot.replot()
def reset_transformation(self) -> None:
"""Reset transformation"""
x0, y0, x1, y1 = self.item.border_rect.get_rect()
self.crop_rect.set_rect(x0, y0, x1, y1)
def apply_transformation(self) -> None:
"""Apply transformation, e.g. crop or rotate"""
# Let's crop!
i_points = self.item.border_rect.get_points()
xmin, ymin = i_points.min(axis=0)
xmax, ymax = i_points.max(axis=0)
xc0, yc0, xc1, yc1 = self.crop_rect.shape.get_rect()
left = max([0, xc0 - xmin])
right = max([0, xmax - xc1])
top = max([0, yc0 - ymin])
bottom = max([0, ymax - yc1])
self.item.set_crop(left, top, right, bottom)
# print "set_crop:", left, top, right, bottom
self.item.compute_bounds()
self.manager.get_plot().replot()
def compute_transformation(self) -> np.ndarray:
"""Compute transformation, return compute output array
Returns:
Compute output array
"""
return get_image_in_shape(self.crop_rect, apply_interpolation=False)
# ------Private API---------------------------------------------------------
def show_crop_rect(self, state: bool) -> None:
"""Show/hide cropping rectangle shape
Args:
state: Show/hide state
"""
self.crop_rect.setVisible(state)
self.crop_rect.label.setVisible(state)
self.manager.get_plot().replot()
[docs]
class RotateCropDialog(QW.QDialog):
"""Rotate & Crop Dialog
Rotate and crop a :py:class:`.TrImageItem` plot item
Args:
parent: Parent widget
title: Dialog title
options: Options dict
resize_to: Resize dialog to (width, height)
edit: Show "Edit" button
toolbar: Show toolbar
"""
def __init__(
self,
parent: QWidget,
title: str | None = None,
options: PlotOptions | dict[str, Any] | None = None,
resize_to: tuple[int, int] | None = None,
edit: bool = True,
toolbar: bool = False,
) -> None:
super().__init__(parent)
win32_fix_title_bar_background(self)
if resize_to is not None:
width, height = resize_to
self.resize(width, height)
self.button_box = None
if title is None:
title = _("Rotate & Crop")
self.setWindowTitle(title)
self.widget = RotateCropWidget(parent=parent, options=options, toolbar=toolbar)
self.setWindowFlags(QC.Qt.WindowType.Window)
buttonhlayout = QW.QHBoxLayout()
self.add_buttons_to_layout(buttonhlayout, edit)
dialogvlayout = QW.QVBoxLayout()
dialogvlayout.addWidget(self.widget.plot_widget.toolbar)
dialogvlayout.addWidget(self.widget)
dialogvlayout.addLayout(buttonhlayout)
self.setLayout(dialogvlayout)
self.transform = self.widget.transform
self.plot_widget = self.widget.plot_widget
self.manager = self.widget.plot_widget.manager
self.accepted.connect(self.transform.accept_changes)
self.rejected.connect(self.transform.reject_changes)
class MultipleRotateCropWidget(basetransform.BaseMultipleTransformWidget):
"""Multiple Rotate & Crop Widget
Rotate and crop several :py:class:`.TrImageItem` plot items"""
TRANSFORM_WIDGET_CLASS = RotateCropWidget