summaryrefslogtreecommitdiff
path: root/silx/gui/plot/Profile.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/Profile.py')
-rw-r--r--silx/gui/plot/Profile.py810
1 files changed, 0 insertions, 810 deletions
diff --git a/silx/gui/plot/Profile.py b/silx/gui/plot/Profile.py
deleted file mode 100644
index 182cf60..0000000
--- a/silx/gui/plot/Profile.py
+++ /dev/null
@@ -1,810 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2018 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.
-#
-# ###########################################################################*/
-"""Utility functions, toolbars and actions to create profile on images
-and stacks of images"""
-
-
-__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel", "H. Payno"]
-__license__ = "MIT"
-__date__ = "24/07/2018"
-
-
-import weakref
-
-import numpy
-
-from silx.image.bilinear import BilinearImage
-
-from .. import icons
-from .. import qt
-from . import items
-from ..colors import cursorColorForColormap
-from . import actions
-from .PlotToolButtons import ProfileToolButton, ProfileOptionToolButton
-from .ProfileMainWindow import ProfileMainWindow
-
-from silx.utils.deprecation import deprecated
-
-
-def _alignedFullProfile(data, origin, scale, position, roiWidth, axis, method):
- """Get a profile along one axis on a stack of images
-
- :param numpy.ndarray data: 3D volume (stack of 2D images)
- The first dimension is the image index.
- :param origin: Origin of image in plot (ox, oy)
- :param scale: Scale of image in plot (sx, sy)
- :param float position: Position of profile line in plot coords
- on the axis orthogonal to the profile direction.
- :param int roiWidth: Width of the profile in image pixels.
- :param int axis: 0 for horizontal profile, 1 for vertical.
- :param str method: method to compute the profile. Can be 'mean' or 'sum'
- :return: profile image + effective ROI area corners in plot coords
- """
- assert axis in (0, 1)
- assert len(data.shape) == 3
- assert method in ('mean', 'sum')
-
- # Convert from plot to image coords
- imgPos = int((position - origin[1 - axis]) / scale[1 - axis])
-
- if axis == 1: # Vertical profile
- # Transpose image to always do a horizontal profile
- data = numpy.transpose(data, (0, 2, 1))
-
- nimages, height, width = data.shape
-
- roiWidth = min(height, roiWidth) # Clip roi width to image size
-
- # Get [start, end[ coords of the roi in the data
- start = int(int(imgPos) + 0.5 - roiWidth / 2.)
- start = min(max(0, start), height - roiWidth)
- end = start + roiWidth
-
- if start < height and end > 0:
- if method == 'mean':
- _fct = numpy.mean
- elif method == 'sum':
- _fct = numpy.sum
- else:
- raise ValueError('method not managed')
- profile = _fct(data[:, max(0, start):min(end, height), :], axis=1).astype(numpy.float32)
- else:
- profile = numpy.zeros((nimages, width), dtype=numpy.float32)
-
- # Compute effective ROI in plot coords
- profileBounds = numpy.array(
- (0, width, width, 0),
- dtype=numpy.float32) * scale[axis] + origin[axis]
- roiBounds = numpy.array(
- (start, start, end, end),
- dtype=numpy.float32) * scale[1 - axis] + origin[1 - axis]
-
- if axis == 0: # Horizontal profile
- area = profileBounds, roiBounds
- else: # vertical profile
- area = roiBounds, profileBounds
-
- return profile, area
-
-
-def _alignedPartialProfile(data, rowRange, colRange, axis, method):
- """Mean of a rectangular region (ROI) of a stack of images
- along a given axis.
-
- Returned values and all parameters are in image coordinates.
-
- :param numpy.ndarray data: 3D volume (stack of 2D images)
- The first dimension is the image index.
- :param rowRange: [min, max[ of ROI rows (upper bound excluded).
- :type rowRange: 2-tuple of int (min, max) with min < max
- :param colRange: [min, max[ of ROI columns (upper bound excluded).
- :type colRange: 2-tuple of int (min, max) with min < max
- :param int axis: The axis along which to take the profile of the ROI.
- 0: Sum rows along columns.
- 1: Sum columns along rows.
- :param str method: method to compute the profile. Can be 'mean' or 'sum'
- :return: Profile image along the ROI as the mean of the intersection
- of the ROI and the image.
- """
- assert axis in (0, 1)
- assert len(data.shape) == 3
- assert rowRange[0] < rowRange[1]
- assert colRange[0] < colRange[1]
- assert method in ('mean', 'sum')
-
- nimages, height, width = data.shape
-
- # Range aligned with the integration direction
- profileRange = colRange if axis == 0 else rowRange
-
- profileLength = abs(profileRange[1] - profileRange[0])
-
- # Subset of the image to use as intersection of ROI and image
- rowStart = min(max(0, rowRange[0]), height)
- rowEnd = min(max(0, rowRange[1]), height)
- colStart = min(max(0, colRange[0]), width)
- colEnd = min(max(0, colRange[1]), width)
-
- if method == 'mean':
- _fct = numpy.mean
- elif method == 'sum':
- _fct = numpy.sum
- else:
- raise ValueError('method not managed')
-
- imgProfile = _fct(data[:, rowStart:rowEnd, colStart:colEnd], axis=axis + 1,
- dtype=numpy.float32)
-
- # Profile including out of bound area
- profile = numpy.zeros((nimages, profileLength), dtype=numpy.float32)
-
- # Place imgProfile in full profile
- offset = - min(0, profileRange[0])
- profile[:, offset:offset + imgProfile.shape[1]] = imgProfile
-
- return profile
-
-
-def createProfile(roiInfo, currentData, origin, scale, lineWidth, method):
- """Create the profile line for the the given image.
-
- :param roiInfo: information about the ROI: start point, end point and
- type ("X", "Y", "D")
- :param numpy.ndarray currentData: the 2D image or the 3D stack of images
- on which we compute the profile.
- :param origin: (ox, oy) the offset from origin
- :type origin: 2-tuple of float
- :param scale: (sx, sy) the scale to use
- :type scale: 2-tuple of float
- :param int lineWidth: width of the profile line
- :param str method: method to compute the profile. Can be 'mean' or 'sum'
- :return: `profile, area, profileName, xLabel`, where:
- - profile is a 2D array of the profiles of the stack of images.
- For a single image, the profile is a curve, so this parameter
- has a shape *(1, len(curve))*
- - area is a tuple of two 1D arrays with 4 values each. They represent
- the effective ROI area corners in plot coords.
- - profileName is a string describing the ROI, meant to be used as
- title of the profile plot
- - xLabel is a string describing the meaning of the X axis on the
- profile plot ("rows", "columns", "distance")
-
- :rtype: tuple(ndarray, (ndarray, ndarray), str, str)
- """
- if currentData is None or roiInfo is None or lineWidth is None:
- raise ValueError("createProfile called with invalide arguments")
-
- # force 3D data (stack of images)
- if len(currentData.shape) == 2:
- currentData3D = currentData.reshape((1,) + currentData.shape)
- elif len(currentData.shape) == 3:
- currentData3D = currentData
-
- roiWidth = max(1, lineWidth)
- roiStart, roiEnd, lineProjectionMode = roiInfo
-
- if lineProjectionMode == 'X': # Horizontal profile on the whole image
- profile, area = _alignedFullProfile(currentData3D,
- origin, scale,
- roiStart[1], roiWidth,
- axis=0,
- method=method)
-
- yMin, yMax = min(area[1]), max(area[1]) - 1
- if roiWidth <= 1:
- profileName = 'Y = %g' % yMin
- else:
- profileName = 'Y = [%g, %g]' % (yMin, yMax)
- xLabel = 'Columns'
-
- elif lineProjectionMode == 'Y': # Vertical profile on the whole image
- profile, area = _alignedFullProfile(currentData3D,
- origin, scale,
- roiStart[0], roiWidth,
- axis=1,
- method=method)
-
- xMin, xMax = min(area[0]), max(area[0]) - 1
- if roiWidth <= 1:
- profileName = 'X = %g' % xMin
- else:
- profileName = 'X = [%g, %g]' % (xMin, xMax)
- xLabel = 'Rows'
-
- else: # Free line profile
-
- # Convert start and end points in image coords as (row, col)
- 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 (int(startPt[0]) == int(endPt[0]) or
- int(startPt[1]) == int(endPt[1])):
- # Profile is aligned with one of the axes
-
- # Convert to int
- startPt = int(startPt[0]), int(startPt[1])
- endPt = int(endPt[0]), int(endPt[1])
-
- # Ensure startPt <= endPt
- if startPt[0] > endPt[0] or startPt[1] > endPt[1]:
- startPt, endPt = endPt, startPt
-
- if startPt[0] == endPt[0]: # Row aligned
- rowRange = (int(startPt[0] + 0.5 - 0.5 * roiWidth),
- int(startPt[0] + 0.5 + 0.5 * roiWidth))
- colRange = startPt[1], endPt[1] + 1
- profile = _alignedPartialProfile(currentData3D,
- rowRange, colRange,
- axis=0,
- method=method)
-
- else: # Column aligned
- rowRange = startPt[0], endPt[0] + 1
- colRange = (int(startPt[1] + 0.5 - 0.5 * roiWidth),
- int(startPt[1] + 0.5 + 0.5 * roiWidth))
- profile = _alignedPartialProfile(currentData3D,
- rowRange, colRange,
- axis=1,
- method=method)
-
- # Convert ranges to plot coords to draw ROI area
- area = (
- numpy.array(
- (colRange[0], colRange[1], colRange[1], colRange[0]),
- dtype=numpy.float32) * scale[0] + origin[0],
- numpy.array(
- (rowRange[0], rowRange[0], rowRange[1], rowRange[1]),
- dtype=numpy.float32) * scale[1] + origin[1])
-
- else: # General case: use bilinear interpolation
-
- # Ensure startPt <= endPt
- if (startPt[1] > endPt[1] or (
- startPt[1] == endPt[1] and startPt[0] > endPt[0])):
- startPt, endPt = endPt, startPt
-
- profile = []
- for slice_idx in range(currentData3D.shape[0]):
- bilinear = BilinearImage(currentData3D[slice_idx, :, :])
-
- profile.append(bilinear.profile_line(
- (startPt[0] - 0.5, startPt[1] - 0.5),
- (endPt[0] - 0.5, endPt[1] - 0.5),
- roiWidth,
- method=method))
- profile = numpy.array(profile)
-
- # Extend ROI with half a pixel on each end, and
- # Convert back to plot coords (x, y)
- length = numpy.sqrt((endPt[0] - startPt[0]) ** 2 +
- (endPt[1] - startPt[1]) ** 2)
- dRow = (endPt[0] - startPt[0]) / length
- dCol = (endPt[1] - startPt[1]) / length
-
- # Extend ROI with half a pixel on each end
- startPt = startPt[0] - 0.5 * dRow, startPt[1] - 0.5 * dCol
- endPt = endPt[0] + 0.5 * dRow, endPt[1] + 0.5 * dCol
-
- # Rotate deltas by 90 degrees to apply line width
- dRow, dCol = dCol, -dRow
-
- area = (
- numpy.array((startPt[1] - 0.5 * roiWidth * dCol,
- startPt[1] + 0.5 * roiWidth * dCol,
- endPt[1] + 0.5 * roiWidth * dCol,
- endPt[1] - 0.5 * roiWidth * dCol),
- dtype=numpy.float32) * scale[0] + origin[0],
- numpy.array((startPt[0] - 0.5 * roiWidth * dRow,
- startPt[0] + 0.5 * roiWidth * dRow,
- endPt[0] + 0.5 * roiWidth * dRow,
- endPt[0] - 0.5 * roiWidth * dRow),
- dtype=numpy.float32) * scale[1] + origin[1])
-
- y0, x0 = startPt
- y1, x1 = endPt
- if x1 == x0 or y1 == y0:
- profileName = 'From (%g, %g) to (%g, %g)' % (x0, y0, x1, y1)
- else:
- m = (y1 - y0) / (x1 - x0)
- b = y0 - m * x0
- profileName = 'y = %g * x %+g ; width=%d' % (m, b, roiWidth)
- xLabel = 'Distance'
-
- return profile, area, profileName, xLabel
-
-
-# ProfileToolBar ##############################################################
-
-class ProfileToolBar(qt.QToolBar):
- """QToolBar providing profile tools operating on a :class:`PlotWindow`.
-
- Attributes:
-
- - plot: Associated :class:`PlotWindow` on which the profile line is drawn.
- - actionGroup: :class:`QActionGroup` of available actions.
-
- To run the following sample code, a QApplication must be initialized.
- First, create a PlotWindow and add a :class:`ProfileToolBar`.
-
- >>> from silx.gui.plot import PlotWindow
- >>> from silx.gui.plot.Profile import ProfileToolBar
-
- >>> plot = PlotWindow() # Create a PlotWindow
- >>> toolBar = ProfileToolBar(plot=plot) # Create a profile toolbar
- >>> plot.addToolBar(toolBar) # Add it to plot
- >>> plot.show() # To display the PlotWindow with the profile toolbar
-
- :param plot: :class:`PlotWindow` instance on which to operate.
- :param profileWindow: Plot widget instance where to
- display the profile curve or None to create one.
- :param str title: See :class:`QToolBar`.
- :param parent: See :class:`QToolBar`.
- """
- # TODO Make it a QActionGroup instead of a QToolBar
-
- _POLYGON_LEGEND = '__ProfileToolBar_ROI_Polygon'
-
- DEFAULT_PROF_METHOD = 'mean'
-
- def __init__(self, parent=None, plot=None, profileWindow=None,
- title='Profile Selection'):
- super(ProfileToolBar, self).__init__(title, parent)
- assert plot is not None
- self._plotRef = weakref.ref(plot)
-
- self._overlayColor = None
- self._defaultOverlayColor = 'red' # update when active image change
- self._method = self.DEFAULT_PROF_METHOD
-
- self._roiInfo = None # Store start and end points and type of ROI
-
- self._profileWindow = profileWindow
- """User provided plot widget in which the profile curve is plotted.
- None if no custom profile plot was provided."""
-
- self._profileMainWindow = None
- """Main window providing 2 profile plot widgets for 1D or 2D profiles.
- The window provides two public methods
- - :meth:`setProfileDimensions`
- - :meth:`getPlot`: return handle on the actual plot widget
- currently being used
- None if the user specified a custom profile plot window.
- """
-
- if self._profileWindow is None:
- self._profileMainWindow = ProfileMainWindow(self)
-
- # Actions
- self._browseAction = actions.mode.ZoomModeAction(self.plot, parent=self)
- self._browseAction.setVisible(False)
-
- self.hLineAction = qt.QAction(
- icons.getQIcon('shape-horizontal'),
- 'Horizontal Profile Mode', None)
- self.hLineAction.setToolTip(
- 'Enables horizontal profile selection mode')
- self.hLineAction.setCheckable(True)
- self.hLineAction.toggled[bool].connect(self._hLineActionToggled)
-
- self.vLineAction = qt.QAction(
- icons.getQIcon('shape-vertical'),
- 'Vertical Profile Mode', None)
- self.vLineAction.setToolTip(
- 'Enables vertical profile selection mode')
- self.vLineAction.setCheckable(True)
- self.vLineAction.toggled[bool].connect(self._vLineActionToggled)
-
- self.lineAction = qt.QAction(
- icons.getQIcon('shape-diagonal'),
- 'Free Line Profile Mode', None)
- self.lineAction.setToolTip(
- 'Enables line profile selection mode')
- self.lineAction.setCheckable(True)
- self.lineAction.toggled[bool].connect(self._lineActionToggled)
-
- self.clearAction = qt.QAction(
- icons.getQIcon('profile-clear'),
- 'Clear Profile', None)
- self.clearAction.setToolTip(
- 'Clear the profile Region of interest')
- self.clearAction.setCheckable(False)
- self.clearAction.triggered.connect(self.clearProfile)
-
- # ActionGroup
- self.actionGroup = qt.QActionGroup(self)
- self.actionGroup.addAction(self._browseAction)
- self.actionGroup.addAction(self.hLineAction)
- self.actionGroup.addAction(self.vLineAction)
- self.actionGroup.addAction(self.lineAction)
-
- # Add actions to ToolBar
- self.addAction(self._browseAction)
- self.addAction(self.hLineAction)
- self.addAction(self.vLineAction)
- self.addAction(self.lineAction)
- self.addAction(self.clearAction)
-
- # Add width spin box to toolbar
- self.addWidget(qt.QLabel('W:'))
- self.lineWidthSpinBox = qt.QSpinBox(self)
- self.lineWidthSpinBox.setRange(1, 1000)
- self.lineWidthSpinBox.setValue(1)
- self.lineWidthSpinBox.valueChanged[int].connect(
- self._lineWidthSpinBoxValueChangedSlot)
- self.addWidget(self.lineWidthSpinBox)
-
- self.methodsButton = ProfileOptionToolButton(parent=self, plot=self)
- self.addWidget(self.methodsButton)
- # TODO: add connection with the signal
- self.methodsButton.sigMethodChanged.connect(self.setProfileMethod)
-
- self.plot.sigInteractiveModeChanged.connect(
- self._interactiveModeChanged)
-
- # Enable toolbar only if there is an active image
- self.setEnabled(self.plot.getActiveImage(just_legend=True) is not None)
- self.plot.sigActiveImageChanged.connect(
- self._activeImageChanged)
-
- # listen to the profile window signals to clear profile polygon on close
- if self.getProfileMainWindow() is not None:
- self.getProfileMainWindow().sigClose.connect(self.clearProfile)
-
- @property
- def plot(self):
- """The :class:`.PlotWidget` associated to the toolbar."""
- return self._plotRef()
-
- @property
- @deprecated(since_version="0.6.0")
- def browseAction(self):
- return self._browseAction
-
- @property
- @deprecated(replacement="getProfilePlot", since_version="0.5.0")
- def profileWindow(self):
- return self.getProfilePlot()
-
- def getProfilePlot(self):
- """Return plot widget in which the profile curve or the
- profile image is plotted.
- """
- if self.getProfileMainWindow() is not None:
- return self.getProfileMainWindow().getPlot()
-
- # in case the user provided a custom plot for profiles
- return self._profileWindow
-
- def getProfileMainWindow(self):
- """Return window containing the profile curve widget.
- This can return *None* if a custom profile plot window was
- specified in the constructor.
- """
- return self._profileMainWindow
-
- def _activeImageChanged(self, previous, legend):
- """Handle active image change: toggle enabled toolbar, update curve"""
- if legend is None:
- self.setEnabled(False)
- else:
- activeImage = self.plot.getActiveImage()
-
- # Disable for empty image
- self.setEnabled(activeImage.getData(copy=False).size > 0)
-
- # Update default profile color
- if isinstance(activeImage, items.ColormapMixIn):
- self._defaultOverlayColor = cursorColorForColormap(
- activeImage.getColormap()['name'])
- else:
- self._defaultOverlayColor = 'black'
-
- self.updateProfile()
-
- def _lineWidthSpinBoxValueChangedSlot(self, value):
- """Listen to ROI width widget to refresh ROI and profile"""
- self.updateProfile()
-
- def _interactiveModeChanged(self, source):
- """Handle plot interactive mode changed:
-
- If changed from elsewhere, disable drawing tool
- """
- if source is not self:
- self.clearProfile()
-
- # Uncheck all drawing profile modes
- self.hLineAction.setChecked(False)
- self.vLineAction.setChecked(False)
- self.lineAction.setChecked(False)
-
- if self.getProfileMainWindow() is not None:
- self.getProfileMainWindow().hide()
-
- def _hLineActionToggled(self, checked):
- """Handle horizontal line profile action toggle"""
- if checked:
- self.plot.setInteractiveMode('draw', shape='hline',
- color=None, source=self)
- self.plot.sigPlotSignal.connect(self._plotWindowSlot)
- else:
- self.plot.sigPlotSignal.disconnect(self._plotWindowSlot)
-
- def _vLineActionToggled(self, checked):
- """Handle vertical line profile action toggle"""
- if checked:
- self.plot.setInteractiveMode('draw', shape='vline',
- color=None, source=self)
- self.plot.sigPlotSignal.connect(self._plotWindowSlot)
- else:
- self.plot.sigPlotSignal.disconnect(self._plotWindowSlot)
-
- def _lineActionToggled(self, checked):
- """Handle line profile action toggle"""
- if checked:
- self.plot.setInteractiveMode('draw', shape='line',
- color=None, source=self)
- self.plot.sigPlotSignal.connect(self._plotWindowSlot)
- else:
- self.plot.sigPlotSignal.disconnect(self._plotWindowSlot)
-
- def _plotWindowSlot(self, event):
- """Listen to Plot to handle drawing events to refresh ROI and profile.
- """
- if event['event'] not in ('drawingProgress', 'drawingFinished'):
- return
-
- checkedAction = self.actionGroup.checkedAction()
- if checkedAction == self.hLineAction:
- lineProjectionMode = 'X'
- elif checkedAction == self.vLineAction:
- lineProjectionMode = 'Y'
- elif checkedAction == self.lineAction:
- lineProjectionMode = 'D'
- else:
- return
-
- roiStart, roiEnd = event['points'][0], event['points'][1]
-
- self._roiInfo = roiStart, roiEnd, lineProjectionMode
- self.updateProfile()
-
- @property
- def overlayColor(self):
- """The color to use for the ROI.
-
- If set to None (the default), the overlay color is adapted to the
- active image colormap and changes if the active image colormap changes.
- """
- return self._overlayColor or self._defaultOverlayColor
-
- @overlayColor.setter
- def overlayColor(self, color):
- self._overlayColor = color
- self.updateProfile()
-
- def clearProfile(self):
- """Remove profile curve and profile area."""
- self._roiInfo = None
- self.updateProfile()
-
- def updateProfile(self):
- """Update the displayed profile and profile ROI.
-
- This uses the current active image of the plot and the current ROI.
- """
- image = self.plot.getActiveImage()
- if image is None:
- return
-
- # Clean previous profile area, and previous curve
- self.plot.remove(self._POLYGON_LEGEND, kind='item')
- self.getProfilePlot().clear()
- self.getProfilePlot().setGraphTitle('')
- self.getProfilePlot().getXAxis().setLabel('X')
- self.getProfilePlot().getYAxis().setLabel('Y')
-
- self._createProfile(currentData=image.getData(copy=False),
- origin=image.getOrigin(),
- scale=image.getScale(),
- colormap=None, # Not used for 2D data
- z=image.getZValue(),
- method=self.getProfileMethod())
-
- def _createProfile(self, currentData, origin, scale, colormap, z, method):
- """Create the profile line for the the given image.
-
- :param numpy.ndarray currentData: the image or the stack of images
- on which we compute the profile
- :param origin: (ox, oy) the offset from origin
- :type origin: 2-tuple of float
- :param scale: (sx, sy) the scale to use
- :type scale: 2-tuple of float
- :param dict colormap: The colormap to use
- :param int z: The z layer of the image
- """
- if self._roiInfo is None:
- return
-
- profile, area, profileName, xLabel = createProfile(
- roiInfo=self._roiInfo,
- currentData=currentData,
- origin=origin,
- scale=scale,
- lineWidth=self.lineWidthSpinBox.value(),
- method=method)
-
- self.getProfilePlot().setGraphTitle(profileName)
-
- dataIs3D = len(currentData.shape) > 2
- if dataIs3D:
- self.getProfilePlot().addImage(profile,
- legend=profileName,
- xlabel=xLabel,
- ylabel="Frame index (depth)",
- colormap=colormap)
- else:
- coords = numpy.arange(len(profile[0]), dtype=numpy.float32)
- # Scale horizontal and vertical profile coordinates
- if self._roiInfo[2] == 'X':
- coords = coords * scale[0] + origin[0]
- elif self._roiInfo[2] == 'Y':
- coords = coords * scale[1] + origin[1]
-
- self.getProfilePlot().addCurve(coords,
- profile[0],
- legend=profileName,
- xlabel=xLabel,
- color=self.overlayColor)
-
- self.plot.addItem(area[0], area[1],
- legend=self._POLYGON_LEGEND,
- color=self.overlayColor,
- shape='polygon', fill=True,
- replace=False, z=z + 1)
-
- self._showProfileMainWindow()
-
- def _showProfileMainWindow(self):
- """If profile window was created by this toolbar,
- try to avoid overlapping with the toolbar's parent window.
- """
- profileMainWindow = self.getProfileMainWindow()
- if profileMainWindow is not None:
- winGeom = self.window().frameGeometry()
- qapp = qt.QApplication.instance()
- screenGeom = qapp.desktop().availableGeometry(self)
- spaceOnLeftSide = winGeom.left()
- spaceOnRightSide = screenGeom.width() - winGeom.right()
-
- profileWindowWidth = profileMainWindow.frameGeometry().width()
- if (profileWindowWidth < spaceOnRightSide):
- # Place profile on the right
- profileMainWindow.move(winGeom.right(), winGeom.top())
- elif(profileWindowWidth < spaceOnLeftSide):
- # Place profile on the left
- profileMainWindow.move(
- max(0, winGeom.left() - profileWindowWidth), winGeom.top())
-
- profileMainWindow.show()
- profileMainWindow.raise_()
- else:
- self.getProfilePlot().show()
- self.getProfilePlot().raise_()
-
- def hideProfileWindow(self):
- """Hide profile window.
- """
- # this method is currently only used by StackView when the perspective
- # is changed
- if self.getProfileMainWindow() is not None:
- self.getProfileMainWindow().hide()
-
- def setProfileMethod(self, method):
- assert method in ('sum', 'mean')
- self._method = method
- self.updateProfile()
-
- def getProfileMethod(self):
- return self._method
-
-
-class Profile3DToolBar(ProfileToolBar):
- def __init__(self, parent=None, stackview=None,
- title='Profile Selection'):
- """QToolBar providing profile tools for an image or a stack of images.
-
- :param parent: the parent QWidget
- :param stackview: :class:`StackView` instance on which to operate.
- :param str title: See :class:`QToolBar`.
- :param parent: See :class:`QToolBar`.
- """
- # TODO: add param profileWindow (specify the plot used for profiles)
- super(Profile3DToolBar, self).__init__(parent=parent,
- plot=stackview.getPlot(),
- title=title)
- self.stackView = stackview
- """:class:`StackView` instance"""
-
- self.profile3dAction = ProfileToolButton(
- parent=self, plot=self.plot)
- self.profile3dAction.computeProfileIn2D()
- self.profile3dAction.setVisible(True)
- self.addWidget(self.profile3dAction)
- self.profile3dAction.sigDimensionChanged.connect(self._setProfileType)
-
- # create the 3D toolbar
- self._profileType = None
- self._setProfileType(2)
- self._method3D = 'sum'
-
- def _setProfileType(self, dimensions):
- """Set the profile type: "1D" for a curve (profile on a single image)
- or "2D" for an image (profile on a stack of images).
-
- :param int dimensions: 1 for a "1D" profile or 2 for a "2D" profile
- """
- # fixme this assumes that we created _profileMainWindow
- self._profileType = "1D" if dimensions == 1 else "2D"
- self.getProfileMainWindow().setProfileType(self._profileType)
- self.updateProfile()
-
- def updateProfile(self):
- """Method overloaded from :class:`ProfileToolBar`,
- to pass the stack of images instead of just the active image.
-
- In 1D profile mode, use the regular parent method.
- """
- if self._profileType == "1D":
- super(Profile3DToolBar, self).updateProfile()
- elif self._profileType == "2D":
- stackData = self.stackView.getCurrentView(copy=False,
- returnNumpyArray=True)
- if stackData is None:
- return
- self.plot.remove(self._POLYGON_LEGEND, kind='item')
- self.getProfilePlot().clear()
- self.getProfilePlot().setGraphTitle('')
- self.getProfilePlot().getXAxis().setLabel('X')
- self.getProfilePlot().getYAxis().setLabel('Y')
- self._createProfile(currentData=stackData[0],
- origin=stackData[1]['origin'],
- scale=stackData[1]['scale'],
- colormap=stackData[1]['colormap'],
- z=stackData[1]['z'],
- method=self.getProfileMethod())
- else:
- raise ValueError(
- "Profile type must be 1D or 2D, not %s" % self._profileType)
-
- def setProfileMethod(self, method):
- assert method in ('sum', 'mean')
- self._method3D = method
- self.updateProfile()
-
- def getProfileMethod(self):
- return self._method3D