diff options
Diffstat (limited to 'silx/gui/plot/tools/profile/rois.py')
-rw-r--r-- | silx/gui/plot/tools/profile/rois.py | 1156 |
1 files changed, 0 insertions, 1156 deletions
diff --git a/silx/gui/plot/tools/profile/rois.py b/silx/gui/plot/tools/profile/rois.py deleted file mode 100644 index eb7e975..0000000 --- a/silx/gui/plot/tools/profile/rois.py +++ /dev/null @@ -1,1156 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2018-2021 European Synchrotron Radiation Facility -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# ###########################################################################*/ -"""This module define ROIs for profile tools. - -.. inheritance-diagram:: - silx.gui.plot.tools.profile.rois - :top-classes: silx.gui.plot.tools.profile.core.ProfileRoiMixIn, silx.gui.plot.items.roi.RegionOfInterest - :parts: 1 - :private-bases: -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "01/12/2020" - -import numpy -import weakref -from concurrent.futures import CancelledError - -from silx.gui import colors - -from silx.gui.plot import items -from silx.gui.plot.items import roi as roi_items -from . import core -from silx.gui import utils -from .....utils.proxy import docstring - - -def _relabelAxes(plot, text): - """Relabel {xlabel} and {ylabel} from this text using the corresponding - plot axis label. If the axis label is empty, label it with "X" and "Y". - - :rtype: str - """ - xLabel = plot.getXAxis().getLabel() - if not xLabel: - xLabel = "X" - yLabel = plot.getYAxis().getLabel() - if not yLabel: - yLabel = "Y" - return text.format(xlabel=xLabel, ylabel=yLabel) - - -def _lineProfileTitle(x0, y0, x1, y1): - """Compute corresponding plot title - - This can be overridden to change title behavior. - - :param float x0: Profile start point X coord - :param float y0: Profile start point Y coord - :param float x1: Profile end point X coord - :param float y1: Profile end point Y coord - :return: Title to use - :rtype: str - """ - if x0 == x1: - title = '{xlabel} = %g; {ylabel} = [%g, %g]' % (x0, y0, y1) - elif y0 == y1: - title = '{ylabel} = %g; {xlabel} = [%g, %g]' % (y0, x0, x1) - else: - m = (y1 - y0) / (x1 - x0) - b = y0 - m * x0 - title = '{ylabel} = %g * {xlabel} %+g' % (m, b) - - return title - - -class _ImageProfileArea(items.Shape): - """This shape displays the location of pixels used to compute the - profile.""" - - def __init__(self, parentRoi): - items.Shape.__init__(self, "polygon") - color = colors.rgba(parentRoi.getColor()) - self.setColor(color) - self.setFill(True) - self.setOverlay(True) - self.setPoints([[0, 0], [0, 0]]) # Else it segfault - - self.__parentRoi = weakref.ref(parentRoi) - parentRoi.sigItemChanged.connect(self._updateAreaProperty) - parentRoi.sigRegionChanged.connect(self._updateArea) - parentRoi.sigProfilePropertyChanged.connect(self._updateArea) - parentRoi.sigPlotItemChanged.connect(self._updateArea) - - def getParentRoi(self): - if self.__parentRoi is None: - return None - parentRoi = self.__parentRoi() - if parentRoi is None: - self.__parentRoi = None - return parentRoi - - def _updateAreaProperty(self, event=None, checkVisibility=True): - parentRoi = self.sender() - if event == items.ItemChangedType.COLOR: - parentRoi._updateItemProperty(event, parentRoi, self) - elif event == items.ItemChangedType.VISIBLE: - if self.getPlotItem() is not None: - parentRoi._updateItemProperty(event, parentRoi, self) - - def _updateArea(self): - roi = self.getParentRoi() - item = roi.getPlotItem() - if item is None: - self.setVisible(False) - return - polygon = self._computePolygon(item) - self.setVisible(True) - polygon = numpy.array(polygon).T - self.setLineStyle("--") - self.setPoints(polygon, copy=False) - - def _computePolygon(self, item): - if not isinstance(item, items.ImageBase): - raise TypeError("Unexpected class %s" % type(item)) - - currentData = item.getValueData(copy=False) - - roi = self.getParentRoi() - origin = item.getOrigin() - scale = item.getScale() - _coords, _profile, area, _profileName, _xLabel = core.createProfile( - roiInfo=roi._getRoiInfo(), - currentData=currentData, - origin=origin, - scale=scale, - lineWidth=roi.getProfileLineWidth(), - method="none") - return area - - -class _SliceProfileArea(items.Shape): - """This shape displays the location a profile in a scatter. - - Each point used to compute the slice are linked together. - """ - - def __init__(self, parentRoi): - items.Shape.__init__(self, "polygon") - color = colors.rgba(parentRoi.getColor()) - self.setColor(color) - self.setFill(True) - self.setOverlay(True) - self.setPoints([[0, 0], [0, 0]]) # Else it segfault - - self.__parentRoi = weakref.ref(parentRoi) - parentRoi.sigItemChanged.connect(self._updateAreaProperty) - parentRoi.sigRegionChanged.connect(self._updateArea) - parentRoi.sigProfilePropertyChanged.connect(self._updateArea) - parentRoi.sigPlotItemChanged.connect(self._updateArea) - - def getParentRoi(self): - if self.__parentRoi is None: - return None - parentRoi = self.__parentRoi() - if parentRoi is None: - self.__parentRoi = None - return parentRoi - - def _updateAreaProperty(self, event=None, checkVisibility=True): - parentRoi = self.sender() - if event == items.ItemChangedType.COLOR: - parentRoi._updateItemProperty(event, parentRoi, self) - elif event == items.ItemChangedType.VISIBLE: - if self.getPlotItem() is not None: - parentRoi._updateItemProperty(event, parentRoi, self) - - def _updateArea(self): - roi = self.getParentRoi() - item = roi.getPlotItem() - if item is None: - self.setVisible(False) - return - polylines = self._computePolylines(roi, item) - if polylines is None: - self.setVisible(False) - return - self.setVisible(True) - self.setLineStyle("--") - self.setPoints(polylines, copy=False) - - def _computePolylines(self, roi, item): - slicing = roi._getSlice(item) - if slicing is None: - return None - xx, yy, _values, _xx_error, _yy_error = item.getData(copy=False) - xx, yy = xx[slicing], yy[slicing] - polylines = numpy.array((xx, yy)).T - if len(polylines) == 0: - return None - return polylines - - -class _DefaultImageProfileRoiMixIn(core.ProfileRoiMixIn): - """Provide common behavior for silx default image profile ROI. - """ - - ITEM_KIND = items.ImageBase - - def __init__(self, parent=None): - core.ProfileRoiMixIn.__init__(self, parent=parent) - self.__method = "mean" - self.__width = 1 - self.sigRegionChanged.connect(self.__regionChanged) - self.sigPlotItemChanged.connect(self.__updateArea) - self.__area = _ImageProfileArea(self) - self.addItem(self.__area) - - def __regionChanged(self): - self.invalidateProfile() - self.__updateArea() - - def setProfileMethod(self, method): - """ - :param str method: method to compute the profile. Can be 'mean' or 'sum' - """ - if self.__method == method: - return - self.__method = method - self.invalidateProperties() - self.invalidateProfile() - - def getProfileMethod(self): - return self.__method - - def setProfileLineWidth(self, width): - if self.__width == width: - return - self.__width = width - self.__updateArea() - self.invalidateProperties() - self.invalidateProfile() - - def getProfileLineWidth(self): - return self.__width - - def __updateArea(self): - plotItem = self.getPlotItem() - if plotItem is None: - self.setLineStyle("-") - else: - self.setLineStyle("--") - - def _getRoiInfo(self): - """Wrapper to allow to reuse the previous Profile code. - - It would be good to remove it at one point. - """ - if isinstance(self, roi_items.HorizontalLineROI): - lineProjectionMode = 'X' - y = self.getPosition() - roiStart = (0, y) - roiEnd = (1, y) - elif isinstance(self, roi_items.VerticalLineROI): - lineProjectionMode = 'Y' - x = self.getPosition() - roiStart = (x, 0) - roiEnd = (x, 1) - elif isinstance(self, roi_items.LineROI): - lineProjectionMode = 'D' - roiStart, roiEnd = self.getEndPoints() - else: - assert False - - return roiStart, roiEnd, lineProjectionMode - - def computeProfile(self, item): - if not isinstance(item, items.ImageBase): - raise TypeError("Unexpected class %s" % type(item)) - - origin = item.getOrigin() - scale = item.getScale() - method = self.getProfileMethod() - lineWidth = self.getProfileLineWidth() - - def createProfile2(currentData): - coords, profile, _area, profileName, xLabel = core.createProfile( - roiInfo=self._getRoiInfo(), - currentData=currentData, - origin=origin, - scale=scale, - lineWidth=lineWidth, - method=method) - return coords, profile, profileName, xLabel - - currentData = item.getValueData(copy=False) - - yLabel = "%s" % str(method).capitalize() - coords, profile, title, xLabel = createProfile2(currentData) - title = title + "; width = %d" % lineWidth - - # Use the axis names from the original plot - profileManager = self.getProfileManager() - plot = profileManager.getPlotWidget() - title = _relabelAxes(plot, title) - xLabel = _relabelAxes(plot, xLabel) - - if isinstance(item, items.ImageRgba): - rgba = item.getData(copy=False) - _coords, r, _profileName, _xLabel = createProfile2(rgba[..., 0]) - _coords, g, _profileName, _xLabel = createProfile2(rgba[..., 1]) - _coords, b, _profileName, _xLabel = createProfile2(rgba[..., 2]) - if rgba.shape[-1] == 4: - _coords, a, _profileName, _xLabel = createProfile2(rgba[..., 3]) - else: - a = [None] - data = core.RgbaProfileData( - coords=coords, - profile=profile[0], - profile_r=r[0], - profile_g=g[0], - profile_b=b[0], - profile_a=a[0], - title=title, - xLabel=xLabel, - yLabel=yLabel, - ) - else: - data = core.CurveProfileData( - coords=coords, - profile=profile[0], - title=title, - xLabel=xLabel, - yLabel=yLabel, - ) - return data - - -class ProfileImageHorizontalLineROI(roi_items.HorizontalLineROI, - _DefaultImageProfileRoiMixIn): - """ROI for an horizontal profile at a location of an image""" - - ICON = 'shape-horizontal' - NAME = 'horizontal line profile' - - def __init__(self, parent=None): - roi_items.HorizontalLineROI.__init__(self, parent=parent) - _DefaultImageProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileImageVerticalLineROI(roi_items.VerticalLineROI, - _DefaultImageProfileRoiMixIn): - """ROI for a vertical profile at a location of an image""" - - ICON = 'shape-vertical' - NAME = 'vertical line profile' - - def __init__(self, parent=None): - roi_items.VerticalLineROI.__init__(self, parent=parent) - _DefaultImageProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileImageLineROI(roi_items.LineROI, - _DefaultImageProfileRoiMixIn): - """ROI for an image profile between 2 points. - - The X profile of this ROI is the projecting into one of the x/y axes, - using its scale and its orientation. - """ - - ICON = 'shape-diagonal' - NAME = 'line profile' - - def __init__(self, parent=None): - roi_items.LineROI.__init__(self, parent=parent) - _DefaultImageProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileImageDirectedLineROI(roi_items.LineROI, - _DefaultImageProfileRoiMixIn): - """ROI for an image profile between 2 points. - - The X profile of the line is displayed projected into the line itself, - using its scale and its orientation. It's the distance from the origin. - """ - - ICON = 'shape-diagonal-directed' - NAME = 'directed line profile' - - def __init__(self, parent=None): - roi_items.LineROI.__init__(self, parent=parent) - _DefaultImageProfileRoiMixIn.__init__(self, parent=parent) - self._handleStart.setSymbol('o') - - def computeProfile(self, item): - if not isinstance(item, items.ImageBase): - raise TypeError("Unexpected class %s" % type(item)) - - from silx.image.bilinear import BilinearImage - - origin = item.getOrigin() - scale = item.getScale() - method = self.getProfileMethod() - lineWidth = self.getProfileLineWidth() - currentData = item.getValueData(copy=False) - - roiInfo = self._getRoiInfo() - roiStart, roiEnd, _lineProjectionMode = roiInfo - - startPt = ((roiStart[1] - origin[1]) / scale[1], - (roiStart[0] - origin[0]) / scale[0]) - endPt = ((roiEnd[1] - origin[1]) / scale[1], - (roiEnd[0] - origin[0]) / scale[0]) - - if numpy.array_equal(startPt, endPt): - return None - - bilinear = BilinearImage(currentData) - profile = bilinear.profile_line( - (startPt[0] - 0.5, startPt[1] - 0.5), - (endPt[0] - 0.5, endPt[1] - 0.5), - lineWidth, - method=method) - - # Compute the line size - lineSize = numpy.sqrt((roiEnd[1] - roiStart[1]) ** 2 + - (roiEnd[0] - roiStart[0]) ** 2) - coords = numpy.linspace(0, lineSize, len(profile), - endpoint=True, - dtype=numpy.float32) - - title = _lineProfileTitle(*roiStart, *roiEnd) - title = title + "; width = %d" % lineWidth - xLabel = "√({xlabel}²+{ylabel}²)" - yLabel = str(method).capitalize() - - # Use the axis names from the original plot - profileManager = self.getProfileManager() - plot = profileManager.getPlotWidget() - xLabel = _relabelAxes(plot, xLabel) - title = _relabelAxes(plot, title) - - data = core.CurveProfileData( - coords=coords, - profile=profile, - title=title, - xLabel=xLabel, - yLabel=yLabel, - ) - return data - - -class _ProfileCrossROI(roi_items.HandleBasedROI, core.ProfileRoiMixIn): - - """ROI to manage a cross of profiles - - It is managed using 2 sub ROIs for vertical and horizontal. - """ - - _kind = "Cross" - """Label for this kind of ROI""" - - _plotShape = "point" - """Plot shape which is used for the first interaction""" - - def __init__(self, parent=None): - roi_items.HandleBasedROI.__init__(self, parent=parent) - core.ProfileRoiMixIn.__init__(self, parent=parent) - self.sigRegionChanged.connect(self.__regionChanged) - self.sigAboutToBeRemoved.connect(self.__aboutToBeRemoved) - self.__position = 0, 0 - self.__vline = None - self.__hline = None - self.__handle = self.addHandle() - self.__handleLabel = self.addLabelHandle() - self.__handleLabel.setText(self.getName()) - self.__inhibitReentance = utils.LockReentrant() - self.computeProfile = None - self.sigItemChanged.connect(self.__updateLineProperty) - - # Make sure the marker is over the ROIs - self.__handle.setZValue(1) - # Create the vline and the hline - self._createSubRois() - - @docstring(roi_items.HandleBasedROI) - def contains(self, position): - roiPos = self.getPosition() - return position[0] == roiPos[0] or position[1] == roiPos[1] - - def setFirstShapePoints(self, points): - pos = points[0] - self.setPosition(pos) - - def getPosition(self): - """Returns the position of this ROI - - :rtype: numpy.ndarray - """ - return self.__position - - def setPosition(self, pos): - """Set the position of this ROI - - :param numpy.ndarray pos: 2d-coordinate of this point - """ - self.__position = pos - with utils.blockSignals(self.__handle): - self.__handle.setPosition(*pos) - with utils.blockSignals(self.__handleLabel): - self.__handleLabel.setPosition(*pos) - self.sigRegionChanged.emit() - - def handleDragUpdated(self, handle, origin, previous, current): - if handle is self.__handle: - self.setPosition(current) - - def __updateLineProperty(self, event=None, checkVisibility=True): - if event == items.ItemChangedType.NAME: - self.__handleLabel.setText(self.getName()) - elif event in [items.ItemChangedType.COLOR, - items.ItemChangedType.VISIBLE]: - lines = [] - if self.__vline: - lines.append(self.__vline) - if self.__hline: - lines.append(self.__hline) - self._updateItemProperty(event, self, lines) - - def _createLines(self, parent): - """Inherit this function to return 2 ROI objects for respectivly - the horizontal, and the vertical lines.""" - raise NotImplementedError() - - def _setProfileManager(self, profileManager): - core.ProfileRoiMixIn._setProfileManager(self, profileManager) - # Connecting the vline and the hline - roiManager = profileManager.getRoiManager() - roiManager.addRoi(self.__vline) - roiManager.addRoi(self.__hline) - - def _createSubRois(self): - hline, vline = self._createLines(parent=None) - for i, line in enumerate([vline, hline]): - line.setPosition(self.__position[i]) - line.setEditable(True) - line.setSelectable(True) - line.setFocusProxy(self) - line.setName("") - self.__vline = vline - self.__hline = hline - vline.sigAboutToBeRemoved.connect(self.__vlineRemoved) - vline.sigRegionChanged.connect(self.__vlineRegionChanged) - hline.sigAboutToBeRemoved.connect(self.__hlineRemoved) - hline.sigRegionChanged.connect(self.__hlineRegionChanged) - - def _getLines(self): - return self.__hline, self.__vline - - def __regionChanged(self): - if self.__inhibitReentance.locked(): - return - x, y = self.getPosition() - hline, vline = self._getLines() - if hline is None: - return - with self.__inhibitReentance: - hline.setPosition(y) - vline.setPosition(x) - - def __vlineRegionChanged(self): - if self.__inhibitReentance.locked(): - return - pos = self.getPosition() - vline = self.__vline - pos = vline.getPosition(), pos[1] - with self.__inhibitReentance: - self.setPosition(pos) - - def __hlineRegionChanged(self): - if self.__inhibitReentance.locked(): - return - pos = self.getPosition() - hline = self.__hline - pos = pos[0], hline.getPosition() - with self.__inhibitReentance: - self.setPosition(pos) - - def __aboutToBeRemoved(self): - vline = self.__vline - hline = self.__hline - # Avoid side remove signals - if hline is not None: - hline.sigAboutToBeRemoved.disconnect(self.__hlineRemoved) - hline.sigRegionChanged.disconnect(self.__hlineRegionChanged) - if vline is not None: - vline.sigAboutToBeRemoved.disconnect(self.__vlineRemoved) - vline.sigRegionChanged.disconnect(self.__vlineRegionChanged) - # Clean up the child - profileManager = self.getProfileManager() - roiManager = profileManager.getRoiManager() - if hline is not None: - roiManager.removeRoi(hline) - self.__hline = None - if vline is not None: - roiManager.removeRoi(vline) - self.__vline = None - - def __hlineRemoved(self): - self.__lineRemoved(isHline=True) - - def __vlineRemoved(self): - self.__lineRemoved(isHline=False) - - def __lineRemoved(self, isHline): - """If any of the lines is removed: disconnect this objects, and let the - other one persist""" - hline, vline = self._getLines() - - hline.sigAboutToBeRemoved.disconnect(self.__hlineRemoved) - vline.sigAboutToBeRemoved.disconnect(self.__vlineRemoved) - hline.sigRegionChanged.disconnect(self.__hlineRegionChanged) - vline.sigRegionChanged.disconnect(self.__vlineRegionChanged) - - self.__hline = None - self.__vline = None - profileManager = self.getProfileManager() - roiManager = profileManager.getRoiManager() - if isHline: - self.__releaseLine(vline) - else: - self.__releaseLine(hline) - roiManager.removeRoi(self) - - def __releaseLine(self, line): - """Release the line in order to make it independent""" - line.setFocusProxy(None) - line.setName(self.getName()) - line.setEditable(self.isEditable()) - line.setSelectable(self.isSelectable()) - - -class ProfileImageCrossROI(_ProfileCrossROI): - """ROI to manage a cross of profiles - - It is managed using 2 sub ROIs for vertical and horizontal. - """ - - ICON = 'shape-cross' - NAME = 'cross profile' - ITEM_KIND = items.ImageBase - - def _createLines(self, parent): - vline = ProfileImageVerticalLineROI(parent=parent) - hline = ProfileImageHorizontalLineROI(parent=parent) - return hline, vline - - def setProfileMethod(self, method): - """ - :param str method: method to compute the profile. Can be 'mean' or 'sum' - """ - hline, vline = self._getLines() - hline.setProfileMethod(method) - vline.setProfileMethod(method) - self.invalidateProperties() - - def getProfileMethod(self): - hline, _vline = self._getLines() - return hline.getProfileMethod() - - def setProfileLineWidth(self, width): - hline, vline = self._getLines() - hline.setProfileLineWidth(width) - vline.setProfileLineWidth(width) - self.invalidateProperties() - - def getProfileLineWidth(self): - hline, _vline = self._getLines() - return hline.getProfileLineWidth() - - -class _DefaultScatterProfileRoiMixIn(core.ProfileRoiMixIn): - """Provide common behavior for silx default scatter profile ROI. - """ - - ITEM_KIND = items.Scatter - - def __init__(self, parent=None): - core.ProfileRoiMixIn.__init__(self, parent=parent) - self.__nPoints = 1024 - self.sigRegionChanged.connect(self.__regionChanged) - - def __regionChanged(self): - self.invalidateProfile() - - # Number of points - - def getNPoints(self): - """Returns the number of points of the profiles - - :rtype: int - """ - return self.__nPoints - - def setNPoints(self, npoints): - """Set the number of points of the profiles - - :param int npoints: - """ - npoints = int(npoints) - if npoints < 1: - raise ValueError("Unsupported number of points: %d" % npoints) - elif npoints != self.__nPoints: - self.__nPoints = npoints - self.invalidateProperties() - self.invalidateProfile() - - def _computeProfile(self, scatter, x0, y0, x1, y1): - """Compute corresponding profile - - :param float x0: Profile start point X coord - :param float y0: Profile start point Y coord - :param float x1: Profile end point X coord - :param float y1: Profile end point Y coord - :return: (points, values) profile data or None - """ - future = scatter._getInterpolator() - try: - interpolator = future.result() - except CancelledError: - return None - if interpolator is None: - return None # Cannot init an interpolator - - nPoints = self.getNPoints() - points = numpy.transpose(( - numpy.linspace(x0, x1, nPoints, endpoint=True), - numpy.linspace(y0, y1, nPoints, endpoint=True))) - - values = interpolator(points) - - if not numpy.any(numpy.isfinite(values)): - return None # Profile outside convex hull - - return points, values - - def computeProfile(self, item): - """Update profile according to current ROI""" - if not isinstance(item, items.Scatter): - raise TypeError("Unexpected class %s" % type(item)) - - # Get end points - if isinstance(self, roi_items.LineROI): - points = self.getEndPoints() - x0, y0 = points[0] - x1, y1 = points[1] - elif isinstance(self, (roi_items.VerticalLineROI, roi_items.HorizontalLineROI)): - profileManager = self.getProfileManager() - plot = profileManager.getPlotWidget() - - if isinstance(self, roi_items.HorizontalLineROI): - x0, x1 = plot.getXAxis().getLimits() - y0 = y1 = self.getPosition() - - elif isinstance(self, roi_items.VerticalLineROI): - x0 = x1 = self.getPosition() - y0, y1 = plot.getYAxis().getLimits() - else: - raise RuntimeError('Unsupported ROI for profile: {}'.format(self.__class__)) - - if x1 < x0 or (x1 == x0 and y1 < y0): - # Invert points - x0, y0, x1, y1 = x1, y1, x0, y0 - - profile = self._computeProfile(item, x0, y0, x1, y1) - if profile is None: - return None - - title = _lineProfileTitle(x0, y0, x1, y1) - points = profile[0] - values = profile[1] - - if (numpy.abs(points[-1, 0] - points[0, 0]) > - numpy.abs(points[-1, 1] - points[0, 1])): - xProfile = points[:, 0] - xLabel = '{xlabel}' - else: - xProfile = points[:, 1] - xLabel = '{ylabel}' - - # Use the axis names from the original - profileManager = self.getProfileManager() - plot = profileManager.getPlotWidget() - title = _relabelAxes(plot, title) - xLabel = _relabelAxes(plot, xLabel) - - data = core.CurveProfileData( - coords=xProfile, - profile=values, - title=title, - xLabel=xLabel, - yLabel='Profile', - ) - return data - - -class ProfileScatterHorizontalLineROI(roi_items.HorizontalLineROI, - _DefaultScatterProfileRoiMixIn): - """ROI for an horizontal profile at a location of a scatter""" - - ICON = 'shape-horizontal' - NAME = 'horizontal line profile' - - def __init__(self, parent=None): - roi_items.HorizontalLineROI.__init__(self, parent=parent) - _DefaultScatterProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileScatterVerticalLineROI(roi_items.VerticalLineROI, - _DefaultScatterProfileRoiMixIn): - """ROI for an horizontal profile at a location of a scatter""" - - ICON = 'shape-vertical' - NAME = 'vertical line profile' - - def __init__(self, parent=None): - roi_items.VerticalLineROI.__init__(self, parent=parent) - _DefaultScatterProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileScatterLineROI(roi_items.LineROI, - _DefaultScatterProfileRoiMixIn): - """ROI for an horizontal profile at a location of a scatter""" - - ICON = 'shape-diagonal' - NAME = 'line profile' - - def __init__(self, parent=None): - roi_items.LineROI.__init__(self, parent=parent) - _DefaultScatterProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileScatterCrossROI(_ProfileCrossROI): - """ROI to manage a cross of profiles for scatters. - """ - - ICON = 'shape-cross' - NAME = 'cross profile' - ITEM_KIND = items.Scatter - - def _createLines(self, parent): - vline = ProfileScatterVerticalLineROI(parent=parent) - hline = ProfileScatterHorizontalLineROI(parent=parent) - return hline, vline - - def getNPoints(self): - """Returns the number of points of the profiles - - :rtype: int - """ - hline, _vline = self._getLines() - return hline.getNPoints() - - def setNPoints(self, npoints): - """Set the number of points of the profiles - - :param int npoints: - """ - hline, vline = self._getLines() - hline.setNPoints(npoints) - vline.setNPoints(npoints) - self.invalidateProperties() - - -class _DefaultScatterProfileSliceRoiMixIn(core.ProfileRoiMixIn): - """Default ROI to allow to slice in the scatter data.""" - - ITEM_KIND = items.Scatter - - def __init__(self, parent=None): - core.ProfileRoiMixIn.__init__(self, parent=parent) - self.__area = _SliceProfileArea(self) - self.addItem(self.__area) - self.sigRegionChanged.connect(self._regionChanged) - self.sigPlotItemChanged.connect(self._updateArea) - - def _regionChanged(self): - self.invalidateProfile() - self._updateArea() - - def _updateArea(self): - plotItem = self.getPlotItem() - if plotItem is None: - self.setLineStyle("-") - else: - self.setLineStyle("--") - - def _getSlice(self, item): - position = self.getPosition() - bounds = item.getCurrentVisualizationParameter(items.Scatter.VisualizationParameter.GRID_BOUNDS) - if isinstance(self, roi_items.HorizontalLineROI): - axis = 1 - elif isinstance(self, roi_items.VerticalLineROI): - axis = 0 - else: - assert False - if position < bounds[0][axis] or position > bounds[1][axis]: - # ROI outside of the scatter bound - return None - - major_order = item.getCurrentVisualizationParameter(items.Scatter.VisualizationParameter.GRID_MAJOR_ORDER) - assert major_order == 'row' - max_grid_yy, max_grid_xx = item.getCurrentVisualizationParameter(items.Scatter.VisualizationParameter.GRID_SHAPE) - - xx, yy, _values, _xx_error, _yy_error = item.getData(copy=False) - if isinstance(self, roi_items.HorizontalLineROI): - axis = yy - max_grid_first = max_grid_yy - max_grid_second = max_grid_xx - major_axis = major_order == 'column' - elif isinstance(self, roi_items.VerticalLineROI): - axis = xx - max_grid_first = max_grid_xx - max_grid_second = max_grid_yy - major_axis = major_order == 'row' - else: - assert False - - def argnearest(array, value): - array = numpy.abs(array - value) - return numpy.argmin(array) - - if major_axis: - # slice in the middle of the scatter - start = max_grid_second // 2 * max_grid_first - vslice = axis[start:start + max_grid_second] - index = argnearest(vslice, position) - slicing = slice(index, None, max_grid_first) - else: - # slice in the middle of the scatter - vslice = axis[max_grid_second // 2::max_grid_second] - index = argnearest(vslice, position) - start = index * max_grid_second - slicing = slice(start, start + max_grid_second) - - return slicing - - def computeProfile(self, item): - if not isinstance(item, items.Scatter): - raise TypeError("Unsupported %s item" % type(item)) - - slicing = self._getSlice(item) - if slicing is None: - # ROI out of bounds - return None - - _xx, _yy, values, _xx_error, _yy_error = item.getData(copy=False) - profile = values[slicing] - - if isinstance(self, roi_items.HorizontalLineROI): - title = "Horizontal slice" - xLabel = "{xlabel} index" - elif isinstance(self, roi_items.VerticalLineROI): - title = "Vertical slice" - xLabel = "{ylabel} index" - else: - assert False - - # Use the axis names from the original plot - profileManager = self.getProfileManager() - plot = profileManager.getPlotWidget() - xLabel = _relabelAxes(plot, xLabel) - - data = core.CurveProfileData( - coords=numpy.arange(len(profile)), - profile=profile, - title=title, - xLabel=xLabel, - yLabel="Profile", - ) - return data - - -class ProfileScatterHorizontalSliceROI(roi_items.HorizontalLineROI, - _DefaultScatterProfileSliceRoiMixIn): - """ROI for an horizontal profile at a location of a scatter - using data slicing. - """ - - ICON = 'slice-horizontal' - NAME = 'horizontal data slice profile' - - def __init__(self, parent=None): - roi_items.HorizontalLineROI.__init__(self, parent=parent) - _DefaultScatterProfileSliceRoiMixIn.__init__(self, parent=parent) - - -class ProfileScatterVerticalSliceROI(roi_items.VerticalLineROI, - _DefaultScatterProfileSliceRoiMixIn): - """ROI for a vertical profile at a location of a scatter - using data slicing. - """ - - ICON = 'slice-vertical' - NAME = 'vertical data slice profile' - - def __init__(self, parent=None): - roi_items.VerticalLineROI.__init__(self, parent=parent) - _DefaultScatterProfileSliceRoiMixIn.__init__(self, parent=parent) - - -class ProfileScatterCrossSliceROI(_ProfileCrossROI): - """ROI to manage a cross of slicing profiles on scatters. - """ - - ICON = 'slice-cross' - NAME = 'cross data slice profile' - ITEM_KIND = items.Scatter - - def _createLines(self, parent): - vline = ProfileScatterVerticalSliceROI(parent=parent) - hline = ProfileScatterHorizontalSliceROI(parent=parent) - return hline, vline - - -class _DefaultImageStackProfileRoiMixIn(_DefaultImageProfileRoiMixIn): - - ITEM_KIND = items.ImageStack - - def __init__(self, parent=None): - super(_DefaultImageStackProfileRoiMixIn, self).__init__(parent=parent) - self.__profileType = "1D" - """Kind of profile""" - - def getProfileType(self): - return self.__profileType - - def setProfileType(self, kind): - assert kind in ["1D", "2D"] - if self.__profileType == kind: - return - self.__profileType = kind - self.invalidateProperties() - self.invalidateProfile() - - def computeProfile(self, item): - if not isinstance(item, items.ImageStack): - raise TypeError("Unexpected class %s" % type(item)) - - kind = self.getProfileType() - if kind == "1D": - result = _DefaultImageProfileRoiMixIn.computeProfile(self, item) - # z = item.getStackPosition() - return result - - assert kind == "2D" - - def createProfile2(currentData): - coords, profile, _area, profileName, xLabel = core.createProfile( - roiInfo=self._getRoiInfo(), - currentData=currentData, - origin=origin, - scale=scale, - lineWidth=self.getProfileLineWidth(), - method=method) - return coords, profile, profileName, xLabel - - currentData = numpy.array(item.getStackData(copy=False)) - origin = item.getOrigin() - scale = item.getScale() - colormap = item.getColormap() - method = self.getProfileMethod() - - coords, profile, profileName, xLabel = createProfile2(currentData) - - data = core.ImageProfileData( - coords=coords, - profile=profile, - title=profileName, - xLabel=xLabel, - yLabel="Profile", - colormap=colormap, - ) - return data - - -class ProfileImageStackHorizontalLineROI(roi_items.HorizontalLineROI, - _DefaultImageStackProfileRoiMixIn): - """ROI for an horizontal profile at a location of a stack of images""" - - ICON = 'shape-horizontal' - NAME = 'horizontal line profile' - - def __init__(self, parent=None): - roi_items.HorizontalLineROI.__init__(self, parent=parent) - _DefaultImageStackProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileImageStackVerticalLineROI(roi_items.VerticalLineROI, - _DefaultImageStackProfileRoiMixIn): - """ROI for an vertical profile at a location of a stack of images""" - - ICON = 'shape-vertical' - NAME = 'vertical line profile' - - def __init__(self, parent=None): - roi_items.VerticalLineROI.__init__(self, parent=parent) - _DefaultImageStackProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileImageStackLineROI(roi_items.LineROI, - _DefaultImageStackProfileRoiMixIn): - """ROI for an vertical profile at a location of a stack of images""" - - ICON = 'shape-diagonal' - NAME = 'line profile' - - def __init__(self, parent=None): - roi_items.LineROI.__init__(self, parent=parent) - _DefaultImageStackProfileRoiMixIn.__init__(self, parent=parent) - - -class ProfileImageStackCrossROI(ProfileImageCrossROI): - """ROI for an vertical profile at a location of a stack of images""" - - ICON = 'shape-cross' - NAME = 'cross profile' - ITEM_KIND = items.ImageStack - - def _createLines(self, parent): - vline = ProfileImageStackVerticalLineROI(parent=parent) - hline = ProfileImageStackHorizontalLineROI(parent=parent) - return hline, vline - - def getProfileType(self): - hline, _vline = self._getLines() - return hline.getProfileType() - - def setProfileType(self, kind): - hline, vline = self._getLines() - hline.setProfileType(kind) - vline.setProfileType(kind) - self.invalidateProperties() |