#
# Copyright © 2009-2010 CEA
# Pierre Raybaut
# Licensed under the terms of the CECILL License
# (see guiqwt/__init__.py for details)
# pylint: disable=C0103
"""
guiqwt.cross_section
--------------------
The `cross_section` module provides cross section related objects:
* :py:class:`guiqwt.cross_section.XCrossSection`: the X-axis
`cross-section panel`
* :py:class:`guiqwt.cross_section.YCrossSection`: the Y-axis
`cross-section panel`
* and other related objects which are exclusively used by the cross-section
panels
Example
~~~~~~~
Simple cross-section demo:
.. literalinclude:: ../guiqwt/tests/cross_section.py
Reference
~~~~~~~~~
.. autoclass:: XCrossSection
:members:
:inherited-members:
.. autoclass:: YCrossSection
:members:
:inherited-members:
"""
import weakref
from guidata.qt.QtGui import (QVBoxLayout, QSizePolicy, QHBoxLayout, QToolBar,
QSpacerItem)
from guidata.qt.QtCore import QSize, QPoint, Qt, SIGNAL
import numpy as np
from guidata.utils import assert_interfaces_valid
from guidata.configtools import get_icon
from guidata.qthelpers import create_action, add_actions
# Local imports
from guiqwt.config import CONF, _
from guiqwt.interfaces import ICSImageItemType, IPanel, IBasePlotItem
from guiqwt.panels import PanelWidget, ID_XCS, ID_YCS, ID_OCS
from guiqwt.curve import CurvePlot, ErrorBarCurveItem
from guiqwt.image import ImagePlot, LUT_MAX, get_image_from_qrect
from guiqwt.styles import CurveParam
from guiqwt.tools import ExportItemDataTool
from guiqwt.geometry import translate, rotate, vector_norm, vector_angle
from guiqwt.image import _scale_tr, INTERP_LINEAR
from guiqwt.signals import (SIG_MARKER_CHANGED, SIG_PLOT_LABELS_CHANGED,
SIG_ANNOTATION_CHANGED, SIG_AXIS_DIRECTION_CHANGED,
SIG_ITEMS_CHANGED, SIG_ACTIVE_ITEM_CHANGED,
SIG_LUT_CHANGED, SIG_CS_CURVE_CHANGED,
SIG_MASK_CHANGED)
from guiqwt.plot import PlotManager
from guiqwt.builder import make
from guiqwt.baseplot import canvas_to_axes, axes_to_canvas
class CrossSectionItem(ErrorBarCurveItem):
"""A Qwt item representing cross section data"""
__implements__ = (IBasePlotItem,)
_inverted = None
def __init__(self, curveparam=None, errorbarparam=None):
ErrorBarCurveItem.__init__(self, curveparam, errorbarparam)
self.perimage_mode = True
self.autoscale_mode = True
self.apply_lut = False
self.source = None
def set_source_image(self, src):
"""
Set source image
(source: object with methods 'get_xsection' and 'get_ysection',
e.g. objects derived from guiqwt.image.BaseImageItem)
"""
self.source = weakref.ref(src)
def get_source_image(self):
if self.source is not None:
return self.source()
def get_cross_section(self, obj):
"""Get cross section data from source image"""
raise NotImplementedError
def clear_data(self):
self.set_data(np.array([]), np.array([]), None, None)
self.plot().emit(SIG_CS_CURVE_CHANGED, self)
def update_curve_data(self, obj):
sectx, secty = self.get_cross_section(obj)
if secty.size == 0 or np.all(np.isnan(secty)):
sectx, secty = np.array([]), np.array([])
if self._inverted:
self.process_curve_data(secty, sectx)
else:
self.process_curve_data(sectx, secty)
def process_curve_data(self, x, y, dx=None, dy=None):
"""
Override this method to process data
before updating the displayed curve
"""
self.set_data(x, y, dx, dy)
def update_item(self, obj):
plot = self.plot()
if not plot:
return
source = self.get_source_image()
if source is None or not plot.isVisible():
return
self.update_curve_data(obj)
self.plot().emit(SIG_CS_CURVE_CHANGED, self)
if not self.autoscale_mode:
self.update_scale()
def update_scale(self):
raise NotImplementedError
def get_rectangular_area(obj):
"""
Return rectangular area covered by object
Return None if object does not support this feature
(like markers, points, ...)
"""
if hasattr(obj, 'get_rect'):
return obj.get_rect()
def get_object_coordinates(obj):
"""Return Marker or PointShape/AnnotatedPoint object coordinates"""
if hasattr(obj, 'get_pos'):
return obj.get_pos()
else:
return obj.xValue(), obj.yValue()
def get_plot_x_section(obj, apply_lut=False):
"""
Return plot cross section along x-axis,
at the y value defined by 'obj', a Marker/AnnotatedPoint object
"""
_x0, y0 = get_object_coordinates(obj)
plot = obj.plot()
xmap = plot.canvasMap(plot.X_BOTTOM)
xc0, xc1 = xmap.p1(), xmap.p2()
_xc0, yc0 = axes_to_canvas(obj, 0, y0)
if plot.get_axis_direction("left"):
yc1 = yc0+1
else:
yc1 = yc0-1
try:
#TODO: eventually add an option to apply interpolation algorithm
data = get_image_from_qrect(plot, QPoint(xc0, yc0), QPoint(xc1, yc1),
apply_lut=apply_lut,
apply_interpolation=False)
except (ValueError, ZeroDivisionError, TypeError):
return np.array([]), np.array([])
y = data.mean(axis=0)
x0, _y0 = canvas_to_axes(obj, QPoint(xc0, yc0))
x1, _y1 = canvas_to_axes(obj, QPoint(xc1, yc1))
x = np.linspace(x0, x1, len(y))
return x, y
def get_plot_y_section(obj, apply_lut=False):
"""
Return plot cross section along y-axis,
at the x value defined by 'obj', a Marker/AnnotatedPoint object
"""
x0, _y0 = get_object_coordinates(obj)
plot = obj.plot()
ymap = plot.canvasMap(plot.Y_LEFT)
yc0, yc1 = ymap.p1(), ymap.p2()
if plot.get_axis_direction("left"):
yc1, yc0 = yc0, yc1
xc0, _yc0 = axes_to_canvas(obj, x0, 0)
xc1 = xc0+1
try:
data = get_image_from_qrect(plot, QPoint(xc0, yc0), QPoint(xc1, yc1),
apply_lut=apply_lut,
apply_interpolation=False)
except (ValueError, ZeroDivisionError, TypeError):
return np.array([]), np.array([])
y = data.mean(axis=1)
_x0, y0 = canvas_to_axes(obj, QPoint(xc0, yc0))
_x1, y1 = canvas_to_axes(obj, QPoint(xc1, yc1))
x = np.linspace(y0, y1, len(y))
return x, y
def get_plot_average_x_section(obj, apply_lut=False):
"""
Return cross section along x-axis, averaged on ROI defined by 'obj'
'obj' is an AbstractShape object supporting the 'get_rect' method
(RectangleShape, AnnotatedRectangle, etc.)
"""
x0, y0, x1, y1 = obj.get_rect()
xc0, yc0 = axes_to_canvas(obj, x0, y0)
xc1, yc1 = axes_to_canvas(obj, x1, y1)
invert = False
if xc0 > xc1:
invert = True
xc1, xc0 = xc0, xc1
ydir = obj.plot().get_axis_direction("left")
if (ydir and yc0 > yc1) or (not ydir and yc0 < yc1):
yc1, yc0 = yc0, yc1
try:
data = get_image_from_qrect(obj.plot(),
QPoint(xc0, yc0), QPoint(xc1, yc1),
apply_lut=apply_lut,
apply_interpolation=False)
except (ValueError, ZeroDivisionError, TypeError):
return np.array([]), np.array([])
y = data.mean(axis=0)
if invert:
y = y[::-1]
x = np.linspace(x0, x1, len(y))
return x, y
def get_plot_average_y_section(obj, apply_lut=False):
"""
Return cross section along y-axis, averaged on ROI defined by 'obj'
'obj' is an AbstractShape object supporting the 'get_rect' method
(RectangleShape, AnnotatedRectangle, etc.)
"""
x0, y0, x1, y1 = obj.get_rect()
xc0, yc0 = axes_to_canvas(obj, x0, y0)
xc1, yc1 = axes_to_canvas(obj, x1, y1)
invert = False
ydir = obj.plot().get_axis_direction("left")
if (ydir and yc0 > yc1) or (not ydir and yc0 < yc1):
invert = True
yc1, yc0 = yc0, yc1
if xc0 > xc1:
xc1, xc0 = xc0, xc1
try:
data = get_image_from_qrect(obj.plot(),
QPoint(xc0, yc0), QPoint(xc1, yc1),
apply_lut=apply_lut,
apply_interpolation=False)
except (ValueError, ZeroDivisionError, TypeError):
return np.array([]), np.array([])
y = data.mean(axis=1)
x = np.linspace(y0, y1, len(y))
if invert:
x = x[::-1]
return x, y
class XCrossSectionItem(CrossSectionItem):
"""A Qwt item representing x-axis cross section data"""
_inverted = False
def get_cross_section(self, obj):
"""Get x-cross section data from source image"""
source = self.get_source_image()
rect = get_rectangular_area(obj)
if rect is None:
# Object is a marker or an annotated point
_x0, y0 = get_object_coordinates(obj)
if self.perimage_mode:
return source.get_xsection(y0, apply_lut=self.apply_lut)
else:
return get_plot_x_section(obj, apply_lut=self.apply_lut)
else:
if self.perimage_mode:
x0, y0, x1, y1 = rect
return source.get_average_xsection(x0, y0, x1, y1,
apply_lut=self.apply_lut)
else:
return get_plot_average_x_section(obj, apply_lut=self.apply_lut)
def update_scale(self):
plot = self.plot()
axis_id = plot.X_BOTTOM
source = self.get_source_image()
sdiv = source.plot().axisScaleDiv(axis_id)
plot.setAxisScale(axis_id, sdiv.lowerBound(), sdiv.upperBound())
plot.replot()
class YCrossSectionItem(CrossSectionItem):
"""A Qwt item representing y-axis cross section data"""
_inverted = True
def get_cross_section(self, obj):
"""Get y-cross section data from source image"""
source = self.get_source_image()
rect = get_rectangular_area(obj)
if rect is None:
# Object is a marker or an annotated point
x0, _y0 = get_object_coordinates(obj)
if self.perimage_mode:
return source.get_ysection(x0, apply_lut=self.apply_lut)
else:
return get_plot_y_section(obj, apply_lut=self.apply_lut)
else:
if self.perimage_mode:
x0, y0, x1, y1 = rect
return source.get_average_ysection(x0, y0, x1, y1,
apply_lut=self.apply_lut)
else:
return get_plot_average_y_section(obj, apply_lut=self.apply_lut)
def update_scale(self):
plot = self.plot()
axis_id = plot.Y_LEFT
source = self.get_source_image()
sdiv = source.plot().axisScaleDiv(axis_id)
plot.setAxisScale(axis_id, sdiv.lowerBound(), sdiv.upperBound())
plot.replot()
LUT_AXIS_TITLE = _("LUT scale")+(" (0-%d)" % LUT_MAX)
class CrossSectionPlot(CurvePlot):
"""Cross section plot"""
CURVE_LABEL = _("Cross section")
LABEL_TEXT = _("Enable a marker")
_height = None
_width = None
CS_AXIS = None
Z_AXIS = None
Z_MAX_MAJOR = 5
CURVETYPE = None
SHADE = .2
def __init__(self, parent=None):
super(CrossSectionPlot, self).__init__(parent=parent, title="",
section="cross_section")
self.perimage_mode = True
self.autoscale_mode = True
self.autorefresh_mode = True
self.apply_lut = False
self.single_source = False
self.last_obj = None
self.known_items = {}
self._shapes = {}
self.curveparam = CurveParam(_("Curve"), icon="curve.png")
self.set_curve_style("cross_section", "curve")
if self._height is not None:
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
elif self._width is not None:
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
self.label = make.label(self.LABEL_TEXT, "C", (0,0), "C")
self.label.set_readonly(True)
self.add_item(self.label)
self.setAxisMaxMajor(self.Z_AXIS, self.Z_MAX_MAJOR)
self.setAxisMaxMinor(self.Z_AXIS, 0)
def set_curve_style(self, section, option):
self.curveparam.read_config(CONF, section, option)
self.curveparam.curvetype = self.CURVETYPE
self.curveparam.label = self.CURVE_LABEL
def connect_plot(self, plot):
if not isinstance(plot, ImagePlot):
# Connecting only to image plot widgets (allow mixing image and
# curve widgets for the same plot manager -- e.g. in pyplot)
return
self.connect(plot, SIG_ITEMS_CHANGED, self.items_changed)
self.connect(plot, SIG_LUT_CHANGED, self.lut_changed)
self.connect(plot, SIG_MASK_CHANGED, lambda item: self.update_plot())
self.connect(plot, SIG_ACTIVE_ITEM_CHANGED, self.active_item_changed)
self.connect(plot, SIG_MARKER_CHANGED, self.marker_changed)
self.connect(plot, SIG_ANNOTATION_CHANGED, self.shape_changed)
self.connect(plot, SIG_PLOT_LABELS_CHANGED, self.plot_labels_changed)
self.connect(plot, SIG_AXIS_DIRECTION_CHANGED, self.axis_dir_changed)
self.plot_labels_changed(plot)
for axis_id in plot.AXIS_IDS:
self.axis_dir_changed(plot, axis_id)
self.items_changed(plot)
def register_shape(self, plot, shape, final, refresh=True):
known_shapes = self._shapes.get(plot, [])
if shape in known_shapes:
return
self._shapes[plot] = known_shapes+[shape]
self.update_plot(shape, refresh=refresh and self.autorefresh_mode)
def unregister_shape(self, shape):
for plot in self._shapes:
shapes = self._shapes[plot]
if shape in shapes:
shapes.pop(shapes.index(shape))
if len(shapes) == 0 or shape is self.get_last_obj():
for curve in self.get_cross_section_curves():
curve.clear_data()
self.replot()
break
def create_cross_section_item(self):
raise NotImplementedError
def add_cross_section_item(self, source):
curve = self.create_cross_section_item()
curve.set_source_image(source)
curve.set_readonly(True)
self.add_item(curve, z=0)
self.known_items[source] = curve
def get_cross_section_curves(self):
return self.known_items.values()
def items_changed(self, plot):
# Del obsolete cross section items
new_sources = plot.get_items(item_type=ICSImageItemType)
for source in self.known_items.copy():
if source not in new_sources:
curve = self.known_items.pop(source)
curve.clear_data() # useful to emit SIG_CS_CURVE_CHANGED
# (eventually notify other panels that the
# cross section curve is now empty)
self.del_item(curve)
if not new_sources:
self.replot()
return
self.curveparam.shade = self.SHADE/len(new_sources)
for source in new_sources:
if source not in self.known_items and source.isVisible():
if not self.single_source or not self.known_items:
self.add_cross_section_item(source=source)
def active_item_changed(self, plot):
"""Active item has just changed"""
self.shape_changed(plot.get_active_item())
def plot_labels_changed(self, plot):
"""Plot labels have changed"""
raise NotImplementedError
def axis_dir_changed(self, plot, axis_id):
"""An axis direction has changed"""
raise NotImplementedError
def marker_changed(self, marker):
self.update_plot(marker)
def is_shape_known(self, shape):
for shapes in self._shapes.values():
if shape in shapes:
return True
else:
return False
def shape_changed(self, shape):
if self.autorefresh_mode:
if self.is_shape_known(shape):
self.update_plot(shape)
def get_last_obj(self):
if self.last_obj is not None:
return self.last_obj()
def update_plot(self, obj=None, refresh=True):
"""
Update cross section curve(s) associated to object *obj*
*obj* may be a marker or a rectangular shape
(see :py:class:`guiqwt.tools.CrossSectionTool`
and :py:class:`guiqwt.tools.AverageCrossSectionTool`)
If obj is None, update the cross sections of the last active object
"""
if obj is None:
obj = self.get_last_obj()
if obj is None:
return
else:
self.last_obj = weakref.ref(obj)
if obj.plot() is None:
self.unregister_shape(obj)
return
if self.label.isVisible():
self.label.hide()
for index, (_item, curve) in enumerate(self.known_items.iteritems()):
if not self.perimage_mode and index > 0:
curve.hide()
else:
curve.show()
curve.perimage_mode = self.perimage_mode
curve.autoscale_mode = self.autoscale_mode
curve.apply_lut = self.apply_lut
if refresh:
curve.update_item(obj)
if self.autoscale_mode:
self.do_autoscale(replot=True)
if self.apply_lut:
self.set_axis_title(self.Z_AXIS, LUT_AXIS_TITLE)
self.set_axis_color(self.Z_AXIS, "red")
else:
self.plot_labels_changed(obj.plot())
def toggle_perimage_mode(self, state):
self.perimage_mode = state
self.update_plot()
def toggle_autoscale(self, state):
self.autoscale_mode = state
self.update_plot()
def toggle_autorefresh(self, state):
self.autorefresh_mode = state
if state:
self.update_plot()
def toggle_apply_lut(self, state):
self.apply_lut = state
self.update_plot()
def lut_changed(self, plot):
if self.apply_lut:
self.update_plot()
class HorizontalCrossSectionPlot(CrossSectionPlot):
CS_AXIS = CurvePlot.X_BOTTOM
Z_AXIS = CurvePlot.Y_LEFT
CURVETYPE = "Yfx"
def plot_labels_changed(self, plot):
"""Plot labels have changed"""
self.set_axis_title("left", plot.get_axis_title("right"))
self.set_axis_title("bottom", plot.get_axis_title("bottom"))
self.set_axis_color("left", plot.get_axis_color("right"))
self.set_axis_color("bottom", plot.get_axis_color("bottom"))
def axis_dir_changed(self, plot, axis_id):
"""An axis direction has changed"""
if axis_id == plot.X_BOTTOM:
self.set_axis_direction("bottom", plot.get_axis_direction("bottom"))
self.replot()
class VerticalCrossSectionPlot(CrossSectionPlot):
CS_AXIS = CurvePlot.Y_LEFT
Z_AXIS = CurvePlot.X_BOTTOM
Z_MAX_MAJOR = 3
CURVETYPE = "Xfy"
def plot_labels_changed(self, plot):
"""Plot labels have changed"""
self.set_axis_title("bottom", plot.get_axis_title("right"))
self.set_axis_title("left", plot.get_axis_title("left"))
self.set_axis_color("bottom", plot.get_axis_color("right"))
self.set_axis_color("left", plot.get_axis_color("left"))
def axis_dir_changed(self, plot, axis_id):
"""An axis direction has changed"""
if axis_id == plot.Y_LEFT:
self.set_axis_direction("left", plot.get_axis_direction("left"))
self.replot()
class XCrossSectionPlot(HorizontalCrossSectionPlot):
"""X-axis cross section plot"""
_height = 130
def sizeHint(self):
return QSize(self.width(), self._height)
def create_cross_section_item(self):
return XCrossSectionItem(self.curveparam)
class YCrossSectionPlot(VerticalCrossSectionPlot):
"""Y-axis cross section plot"""
_width = 140
def sizeHint(self):
return QSize(self._width, self.height())
def create_cross_section_item(self):
return YCrossSectionItem(self.curveparam)
class CrossSectionWidget(PanelWidget):
PANEL_ID = None
PANEL_TITLE = _("Cross section tool")
PANEL_ICON = "csection.png"
CrossSectionPlotKlass = None
__implements__ = (IPanel,)
def __init__(self, parent=None):
super(CrossSectionWidget, self).__init__(parent)
self.export_ac = None
self.autoscale_ac = None
self.refresh_ac = None
self.autorefresh_ac = None
self.manager = None # manager for the associated image plot
self.local_manager = PlotManager(self)
self.cs_plot = self.CrossSectionPlotKlass(parent)
self.connect(self.cs_plot, SIG_CS_CURVE_CHANGED,
self.cs_curve_has_changed)
self.export_tool = None
self.setup_plot()
self.toolbar = QToolBar(self)
self.toolbar.setOrientation(Qt.Vertical)
self.setup_widget()
def set_options(self, autoscale=None, autorefresh=None):
assert self.manager is not None, "Panel '%s' must be registered to plot manager before changing options" % self.PANEL_ID
if autoscale is not None:
self.autoscale_ac.setChecked(autoscale)
if autorefresh is not None:
self.autorefresh_ac.setChecked(autorefresh)
def setup_plot(self):
# Configure the local manager
lman = self.local_manager
lman.add_plot(self.cs_plot)
lman.register_all_curve_tools()
self.export_tool = lman.get_tool(ExportItemDataTool)
def setup_widget(self):
layout = QHBoxLayout()
layout.addWidget(self.cs_plot)
layout.addWidget(self.toolbar)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def cs_curve_has_changed(self, curve):
"""Cross section curve has just changed"""
# Do something with curve's data for example
pass
def register_panel(self, manager):
"""Register panel to plot manager"""
self.manager = manager
for plot in manager.get_plots():
self.cs_plot.connect_plot(plot)
self.setup_actions()
self.add_actions_to_toolbar()
def configure_panel(self):
"""Configure panel"""
pass
def get_plot(self):
return self.manager.get_active_plot()
def setup_actions(self):
self.export_ac = self.export_tool.action
self.autoscale_ac = create_action(self, _("Auto-scale"),
icon=get_icon('csautoscale.png'),
toggled=self.cs_plot.toggle_autoscale)
self.autoscale_ac.setChecked(self.cs_plot.autoscale_mode)
self.refresh_ac = create_action(self, _("Refresh"),
icon=get_icon('refresh.png'),
triggered=lambda: self.cs_plot.update_plot())
self.autorefresh_ac = create_action(self, _("Auto-refresh"),
icon=get_icon('autorefresh.png'),
toggled=self.cs_plot.toggle_autorefresh)
self.autorefresh_ac.setChecked(self.cs_plot.autorefresh_mode)
def add_actions_to_toolbar(self):
add_actions(self.toolbar, (self.export_ac, self.autoscale_ac,
None, self.refresh_ac, self.autorefresh_ac))
def register_shape(self, shape, final, refresh=True):
plot = self.get_plot()
self.cs_plot.register_shape(plot, shape, final, refresh)
def unregister_shape(self, shape):
self.cs_plot.unregister_shape(shape)
def update_plot(self, obj=None):
"""
Update cross section curve(s) associated to object *obj*
*obj* may be a marker or a rectangular shape
(see :py:class:`guiqwt.tools.CrossSectionTool`
and :py:class:`guiqwt.tools.AverageCrossSectionTool`)
If obj is None, update the cross sections of the last active object
"""
self.cs_plot.update_plot(obj)
assert_interfaces_valid(CrossSectionWidget)
#===============================================================================
# X-Y cross sections
#===============================================================================
class XCrossSection(CrossSectionWidget):
[docs] """X-axis cross section widget"""
PANEL_ID = ID_XCS
OTHER_PANEL_ID = ID_YCS
CrossSectionPlotKlass = XCrossSectionPlot
def __init__(self, parent=None):
super(XCrossSection, self).__init__(parent)
self.peritem_ac = None
self.applylut_ac = None
def set_options(self, autoscale=None, autorefresh=None,
peritem=None, applylut=None):
assert self.manager is not None, "Panel '%s' must be registered to plot manager before changing options" % self.PANEL_ID
if autoscale is not None:
self.autoscale_ac.setChecked(autoscale)
if autorefresh is not None:
self.autorefresh_ac.setChecked(autorefresh)
if peritem is not None:
self.peritem_ac.setChecked(peritem)
if applylut is not None:
self.applylut_ac.setChecked(applylut)
def add_actions_to_toolbar(self):
other = self.manager.get_panel(self.OTHER_PANEL_ID)
if other is None:
add_actions(self.toolbar,
(self.peritem_ac, self.applylut_ac, None,
self.export_ac, self.autoscale_ac,
self.refresh_ac, self.autorefresh_ac))
else:
add_actions(self.toolbar,
(other.peritem_ac, other.applylut_ac, None,
self.export_ac, other.autoscale_ac,
other.refresh_ac, other.autorefresh_ac))
self.connect(other.peritem_ac, SIGNAL("toggled(bool)"),
self.cs_plot.toggle_perimage_mode)
self.connect(other.applylut_ac, SIGNAL("toggled(bool)"),
self.cs_plot.toggle_apply_lut)
self.connect(other.autoscale_ac, SIGNAL("toggled(bool)"),
self.cs_plot.toggle_autoscale)
self.connect(other.refresh_ac, SIGNAL("triggered()"),
lambda: self.cs_plot.update_plot())
self.connect(other.autorefresh_ac, SIGNAL("toggled(bool)"),
self.cs_plot.toggle_autorefresh)
def closeEvent(self, event):
self.hide()
event.ignore()
def setup_actions(self):
CrossSectionWidget.setup_actions(self)
self.peritem_ac = create_action(self, _("Per image cross-section"),
icon=get_icon('csperimage.png'),
toggled=self.cs_plot.toggle_perimage_mode,
tip=_("Enable the per-image cross-section mode, "
"which works directly on image rows/columns.\n"
"That is the fastest method to compute "
"cross-section curves but it ignores "
"image transformations (e.g. rotation)"))
self.applylut_ac = create_action(self,
_("Apply LUT\n(contrast settings)"),
icon=get_icon('csapplylut.png'),
toggled=self.cs_plot.toggle_apply_lut,
tip=_("Apply LUT (Look-Up Table) contrast settings.\n"
"This is the easiest way to compare images "
"which have slightly different level ranges.\n\n"
"Note: LUT is coded over 1024 levels (0...1023)"))
self.peritem_ac.setChecked(True)
self.applylut_ac.setChecked(False)
class YCrossSection(XCrossSection):
[docs] """
Y-axis cross section widget
parent (QWidget): parent widget
position (string): "left" or "right"
"""
PANEL_ID = ID_YCS
OTHER_PANEL_ID = ID_XCS
CrossSectionPlotKlass = YCrossSectionPlot
def __init__(self, parent=None, position="right", xsection_pos="top"):
self.xsection_pos = xsection_pos
self.spacer = QSpacerItem(0, 0)
super(YCrossSection, self).__init__(parent)
self.cs_plot.set_axis_direction("bottom", reverse=position == "left")
def setup_widget(self):
toolbar = self.toolbar
toolbar.setOrientation(Qt.Horizontal)
layout = QVBoxLayout()
if self.xsection_pos == "top":
layout.addSpacerItem(self.spacer)
layout.addWidget(toolbar)
layout.addWidget(self.cs_plot)
if self.xsection_pos == "bottom":
layout.addSpacerItem(self.spacer)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def adjust_height(self, height):
self.spacer.changeSize(0, height, QSizePolicy.Fixed, QSizePolicy.Fixed)
self.layout().invalidate()
#===============================================================================
# Oblique cross sections
#===============================================================================
DEBUG = False
TEMP_ITEM = None
def compute_oblique_section(item, obj):
"""Return oblique averaged cross section"""
global TEMP_ITEM
xa, ya, xb, yb = obj.get_bounding_rect_coords()
x0, y0, x1, y1, x2, y2, x3, y3 = obj.get_rect()
getcpi = item.get_closest_pixel_indexes
ixa, iya = getcpi(xa, ya)
ixb, iyb = getcpi(xb, yb)
ix0, iy0 = getcpi(x0, y0)
ix1, iy1 = getcpi(x1, y1)
ix3, iy3 = getcpi(x3, y3)
destw = vector_norm(ix0, iy0, ix1, iy1)
desth = vector_norm(ix0, iy0, ix3, iy3)
ysign = -1 if obj.plot().get_axis_direction('left') else 1
angle = vector_angle(ix1-ix0, (iy1-iy0)*ysign)
dst_rect = (0, 0, int(destw), int(desth))
dst_image = np.empty((desth, destw), dtype=np.float64)
if isinstance(item.data, np.ma.MaskedArray):
if item.data.dtype in (np.float32, np.float64):
item_data = item.data
else:
item_data = np.ma.array(item.data, dtype=np.float32, copy=True)
data = np.ma.filled(item_data, np.nan)
else:
data = item.data
ixr = .5*(ixb+ixa)
iyr = .5*(iyb+iya)
mat = translate(ixr, iyr)*rotate(-angle)*translate(-.5*destw, -.5*desth)
_scale_tr(data, mat, dst_image, dst_rect,
(1., 0., np.nan), (INTERP_LINEAR,))
if DEBUG:
plot = obj.plot()
if TEMP_ITEM is None:
from guiqwt.builder import make
TEMP_ITEM = make.image(dst_image)
plot.add_item(TEMP_ITEM)
else:
TEMP_ITEM.set_data(dst_image)
if False:
TEMP_ITEM.imageparam.alpha_mask = True
xmin, ymin = ixa, iya
xmax, ymax = xmin+destw, ymin+desth
TEMP_ITEM.imageparam.xmin = xmin
TEMP_ITEM.imageparam.xmax = xmax
TEMP_ITEM.imageparam.ymin = ymin
TEMP_ITEM.imageparam.ymax = ymax
TEMP_ITEM.imageparam.update_image(TEMP_ITEM)
plot.replot()
ydata = np.ma.fix_invalid(dst_image, copy=DEBUG).mean(axis=1)
xdata = item.get_x_values(0, ydata.size)[:ydata.size]
try:
xdata -= xdata[0]
except IndexError:
print xdata, ydata
return xdata, ydata
# Oblique cross section item
class ObliqueCrossSectionItem(CrossSectionItem):
"""A Qwt item representing radially-averaged cross section data"""
def __init__(self, curveparam=None, errorbarparam=None):
CrossSectionItem.__init__(self, curveparam, errorbarparam)
def update_curve_data(self, obj):
source = self.get_source_image()
rect = obj.get_bounding_rect_coords()
if rect is not None and source.data is not None:
# x0, y0, x1, y1 = rect
# angle = obj.get_tr_angle()
sectx, secty = compute_oblique_section(source, obj)
if secty.size == 0 or np.all(np.isnan(secty)):
sectx, secty = np.array([]), np.array([])
self.process_curve_data(sectx, secty, None, None)
def update_scale(self):
pass
# Oblique cross section plot
class ObliqueCrossSectionPlot(HorizontalCrossSectionPlot):
"""Oblique averaged cross section plot"""
PLOT_TITLE = _("Oblique averaged cross section")
CURVE_LABEL = _("Oblique averaged cross section")
LABEL_TEXT = _("Activate the oblique cross section tool")
def __init__(self, parent=None):
super(ObliqueCrossSectionPlot, self).__init__(parent)
self.set_title(self.PLOT_TITLE)
self.single_source = True
def create_cross_section_item(self):
return ObliqueCrossSectionItem(self.curveparam)
def axis_dir_changed(self, plot, axis_id):
"""An axis direction has changed"""
pass
# Oblique cross section panel
class ObliqueCrossSection(CrossSectionWidget):
"""Oblique averaged cross section widget"""
PANEL_ID = ID_OCS
CrossSectionPlotKlass = ObliqueCrossSectionPlot
PANEL_ICON = "csection_oblique.png"