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
def resize_image(original_image, new_shape): """Return resized image :param original_image: :param tuple(int) new_shape: New image shape (rows, columns) :return: New resized image, as a 2D numpy array """ bilinimg = BilinearImage(original_image) row_array, column_array = numpy.meshgrid( numpy.linspace(0, original_image.shape[0], new_shape[0]), numpy.linspace(0, original_image.shape[1], new_shape[1]), indexing="ij") interpolated_values = bilinimg.map_coordinates((row_array, column_array)) interpolated_values.shape = new_shape return interpolated_values
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: `coords, profile, area, profileName, xLabel`, where: - coords is the X coordinate to use to display the profile - 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 the label for X in the profile window :rtype: tuple(ndarray,ndarray,(ndarray,ndarray),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) coords = numpy.arange(len(profile[0]), dtype=numpy.float32) coords = coords * scale[0] + origin[0] yMin, yMax = min(area[1]), max(area[1]) - 1 if roiWidth <= 1: profileName = 'Y = %g' % yMin else: profileName = 'Y = [%g, %g]' % (yMin, yMax) xLabel = 'X' elif lineProjectionMode == 'Y': # Vertical profile on the whole image profile, area = _alignedFullProfile(currentData3D, origin, scale, roiStart[0], roiWidth, axis=1, method=method) coords = numpy.arange(len(profile[0]), dtype=numpy.float32) coords = coords * scale[1] + origin[1] xMin, xMax = min(area[0]), max(area[0]) - 1 if roiWidth <= 1: profileName = 'X = %g' % xMin else: profileName = 'X = [%g, %g]' % (xMin, xMax) xLabel = 'Y' 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 roiStartPt = startPt[0] - 0.5 * dRow, startPt[1] - 0.5 * dCol roiEndPt = 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( (roiStartPt[1] - 0.5 * roiWidth * dCol, roiStartPt[1] + 0.5 * roiWidth * dCol, roiEndPt[1] + 0.5 * roiWidth * dCol, roiEndPt[1] - 0.5 * roiWidth * dCol), dtype=numpy.float32) * scale[0] + origin[0], numpy.array( (roiStartPt[0] - 0.5 * roiWidth * dRow, roiStartPt[0] + 0.5 * roiWidth * dRow, roiEndPt[0] + 0.5 * roiWidth * dRow, roiEndPt[0] - 0.5 * roiWidth * dRow), dtype=numpy.float32) * scale[1] + origin[1]) # Convert start and end points back to plot coords y0 = startPt[0] * scale[1] + origin[1] x0 = startPt[1] * scale[0] + origin[0] y1 = endPt[0] * scale[1] + origin[1] x1 = endPt[1] * scale[0] + origin[0] if startPt[1] == endPt[1]: profileName = 'X = %g; Y = [%g, %g]' % (x0, y0, y1) coords = numpy.arange(len(profile[0]), dtype=numpy.float32) coords = coords * scale[1] + y0 xLabel = 'Y' elif startPt[0] == endPt[0]: profileName = 'Y = %g; X = [%g, %g]' % (y0, x0, x1) coords = numpy.arange(len(profile[0]), dtype=numpy.float32) coords = coords * scale[0] + x0 xLabel = 'X' else: m = (y1 - y0) / (x1 - x0) b = y0 - m * x0 profileName = 'y = %g * x %+g ; width=%d' % (m, b, roiWidth) coords = numpy.linspace(x0, x1, len(profile[0]), endpoint=True, dtype=numpy.float32) xLabel = 'X' return coords, profile, area, profileName, xLabel
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: `coords, profile, area, profileName, xLabel`, where: - coords is the X coordinate to use to display the profile - 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 the label for X in the profile window :rtype: tuple(ndarray,ndarray,(ndarray,ndarray),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) coords = numpy.arange(len(profile[0]), dtype=numpy.float32) coords = coords * scale[0] + origin[0] yMin, yMax = min(area[1]), max(area[1]) - 1 if roiWidth <= 1: profileName = 'Y = %g' % yMin else: profileName = 'Y = [%g, %g]' % (yMin, yMax) xLabel = 'X' elif lineProjectionMode == 'Y': # Vertical profile on the whole image profile, area = _alignedFullProfile(currentData3D, origin, scale, roiStart[0], roiWidth, axis=1, method=method) coords = numpy.arange(len(profile[0]), dtype=numpy.float32) coords = coords * scale[1] + origin[1] xMin, xMax = min(area[0]), max(area[0]) - 1 if roiWidth <= 1: profileName = 'X = %g' % xMin else: profileName = 'X = [%g, %g]' % (xMin, xMax) xLabel = 'Y' 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 roiStartPt = startPt[0] - 0.5 * dRow, startPt[1] - 0.5 * dCol roiEndPt = 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((roiStartPt[1] - 0.5 * roiWidth * dCol, roiStartPt[1] + 0.5 * roiWidth * dCol, roiEndPt[1] + 0.5 * roiWidth * dCol, roiEndPt[1] - 0.5 * roiWidth * dCol), dtype=numpy.float32) * scale[0] + origin[0], numpy.array((roiStartPt[0] - 0.5 * roiWidth * dRow, roiStartPt[0] + 0.5 * roiWidth * dRow, roiEndPt[0] + 0.5 * roiWidth * dRow, roiEndPt[0] - 0.5 * roiWidth * dRow), dtype=numpy.float32) * scale[1] + origin[1]) # Convert start and end points back to plot coords y0 = startPt[0] * scale[1] + origin[1] x0 = startPt[1] * scale[0] + origin[0] y1 = endPt[0] * scale[1] + origin[1] x1 = endPt[1] * scale[0] + origin[0] if startPt[1] == endPt[1]: profileName = 'X = %g; Y = [%g, %g]' % (x0, y0, y1) coords = numpy.arange(len(profile[0]), dtype=numpy.float32) coords = coords * scale[1] + y0 xLabel = 'Y' elif startPt[0] == endPt[0]: profileName = 'Y = %g; X = [%g, %g]' % (y0, x0, x1) coords = numpy.arange(len(profile[0]), dtype=numpy.float32) coords = coords * scale[0] + x0 xLabel = 'X' else: m = (y1 - y0) / (x1 - x0) b = y0 - m * x0 profileName = 'y = %g * x %+g ; width=%d' % (m, b, roiWidth) coords = numpy.linspace(x0, x1, len(profile[0]), endpoint=True, dtype=numpy.float32) xLabel = 'X' return coords, profile, area, profileName, xLabel
def updateProfile(self): """Update the displayed profile and profile ROI. This uses the current active image of the plot and the current ROI. """ # Clean previous profile area, and previous curve self.plot.remove(self._POLYGON_LEGEND, kind='item') self.profileWindow.clear() self.profileWindow.setGraphTitle('') self.profileWindow.setGraphXLabel('X') self.profileWindow.setGraphYLabel('Y') if self._roiInfo is None: return imageData = self.plot.getActiveImage() if imageData is None: return data, params = imageData[0], imageData[4] origin, scale = params['origin'], params['scale'] zActiveImage = params['z'] roiWidth = max(1, self.lineWidthSpinBox.value()) roiStart, roiEnd, lineProjectionMode = self._roiInfo if lineProjectionMode == 'X': # Horizontal profile on the whole image profile, area = self._alignedFullProfile( data, origin, scale, roiStart[1], roiWidth, axis=0) 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 = self._alignedFullProfile( data, origin, scale, roiStart[0], roiWidth, axis=1) 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 = self._alignedPartialProfile( data, rowRange, colRange, axis=0) 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 = self._alignedPartialProfile( data, rowRange, colRange, axis=1) # 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 bilinear = BilinearImage(data) # Offset start/end positions of 0.5 pixel to use pixel center # rather than pixel lower left corner for interpolation # This is only valid if image is displayed with nearest. profile = bilinear.profile_line( (startPt[0] - 0.5, startPt[1] - 0.5), (endPt[0] - 0.5, endPt[1] - 0.5), roiWidth) # 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' coords = numpy.arange(len(profile), dtype=numpy.float32) # TODO coords in plot coords? self.profileWindow.setGraphTitle(profileName) self.profileWindow.addCurve(coords, profile, 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=zActiveImage + 1) if self._ownProfileWindow and not self.profileWindow.isVisible(): # If profile window was created in this widget, # it tries to avoid overlapping this widget when shown winGeom = self.window().frameGeometry() qapp = qt.QApplication.instance() screenGeom = qapp.desktop().availableGeometry(self) spaceOnLeftSide = winGeom.left() spaceOnRightSide = screenGeom.width() - winGeom.right() profileWindowWidth = self.profileWindow.frameGeometry().width() if (profileWindowWidth < spaceOnRightSide or spaceOnRightSide > spaceOnLeftSide): # Place profile on the right self.profileWindow.move(winGeom.right(), winGeom.top()) else: # Not enough place on the right, place profile on the left self.profileWindow.move( max(0, winGeom.left() - profileWindowWidth), winGeom.top()) self.profileWindow.show()