Source code for defdap.base

# Copyright 2021 Mechanics of Microstructures Group
#    at The University of Manchester
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
import networkx as nx

from defdap.quat import Quat
from defdap import plotting
from defdap.plotting import Plot, MapPlot, GrainPlot

from skimage.measure import profile_line

from defdap.utils import reportProgress


[docs]class Map(object): """ Base class for a map. Contains common functionality for all maps. Attributes ---------- grainList : list of defdap.base.Grain List of grains. currGrainId : int ID of last selected grain. """ def __init__(self): self.xDim = None self.yDim = None self.grainList = None self.currGrainId = None # ID of last selected grain self.homogPoints = [] self.proxigramArr = None self.neighbourNetwork = None self.grainPlot = None self.profilePlot = None def __len__(self): return len(self.grainList) # allow array like getting of grains def __getitem__(self, key): # Check that grains have been detected in the map self.checkGrainsDetected() return self.grainList[key] @property def shape(self): return self.yDim, self.xDim
[docs] def checkGrainsDetected(self, raiseExc=True): """Check if grains have been detected. Parameters ---------- raiseExc : bool If True then an expception is raised if grains have not been detected. Returns ------- bool: True if grains detected, False otherwise. Raises ------- Exception If grains not detected. """ if (self.grainList is None or type(self.grainList) is not list or len(self.grainList) < 1): if raiseExc: raise Exception("No grains detected.") else: return False return True
[docs] def plotGrainNumbers(self, dilateBoundaries=False, ax=None, **kwargs): """Plot a map with grains numbered. Parameters ---------- dilateBoundaries : bool, optional Set to true to dilate boundaries. ax : matplotlib.axes.Axes, optional axis to plot on, if not provided the current active axis is used. kwargs : dict, optional Keyword arguments passed to :func:`defdap.plotting.MapPlot.addGrainNumbers` Returns ------- defdap.plotting.MapPlot """ plot = plotting.MapPlot(self, ax=ax) plot.addGrainBoundaries(colour='black', dilate=dilateBoundaries) plot.addGrainNumbers(**kwargs) return plot
[docs] def locateGrainID(self, clickEvent=None, displaySelected=False, **kwargs): """Interactive plot for identifying grains. Parameters ---------- clickEvent : optional Click handler to use. displaySelected : bool, optional If true, plot slip traces for grain selected by click. kwargs : dict, optional Keyword arguments passed to :func:`defdap.base.Map.plotDefault` """ # Check that grains have been detected in the map self.checkGrainsDetected() # reset current selected grain and plot euler map with click handler self.currGrainId = None plot = self.plotDefault(makeInteractive=True, **kwargs) if clickEvent is None: # default click handler which highlights grain and prints id plot.addEventHandler( 'button_press_event', lambda e, p: self.clickGrainID(e, p, displaySelected) ) else: # click handler loaded in as parameter. Pass current map # object to it. plot.addEventHandler('button_press_event', clickEvent) return plot
[docs] def clickGrainID(self, event, plot, displaySelected): """Event handler to capture clicking on a map. Parameters ---------- event : Click event. plot : defdap.plotting.MapPlot Plot to capture clicks from. displaySelected : bool If true, plot the selected grain alone in pop-out window. """ # check if click was on the map if event.inaxes is not plot.ax: return # grain id of selected grain self.currGrainId = int(self.grains[int(event.ydata), int(event.xdata)] - 1) print("Grain ID: {}".format(self.currGrainId)) # update the grain highlights layer in the plot plot.addGrainHighlights([self.currGrainId], alpha=self.highlightAlpha) if displaySelected: currGrain = self[self.currGrainId] if self.grainPlot is None or not self.grainPlot.exists: self.grainPlot = currGrain.plotDefault(makeInteractive=True) else: self.grainPlot.clear() self.grainPlot.callingGrain = currGrain currGrain.plotDefault(plot=self.grainPlot) self.grainPlot.draw()
[docs] def drawLineProfile(self, **kwargs): """Interactive plot for drawing a line profile of data. Parameters ---------- kwargs : dict, optional Keyword arguments passed to :func:`defdap.base.Map.plotDefault` """ plot = self.plotDefault(makeInteractive=True, **kwargs) plot.addEventHandler('button_press_event', plot.lineSlice) plot.addEventHandler('button_release_event', lambda e, p: plot.lineSlice(e, p, action=self.calcLineProfile)) return plot
[docs] def calcLineProfile(self, plot, startEnd, **kwargs): """Calculate and plot the line profile. Parameters ---------- plot : defdap.plotting.MapPlot Plot to calculate the line profile for. startEnd : array_like Selected points (x0, y0, x1, y1). kwargs : dict, optional Keyword arguments passed to :func:`matplotlib.pyplot.plot` """ x0, y0 = startEnd[0:2] x1, y1 = startEnd[2:4] profile_length = np.sqrt((y1 - y0) ** 2 + (x1 - x0) ** 2) # Extract the values along the line zi = profile_line( plot.imgLayers[0].get_array(), (startEnd[1], startEnd[0]), (startEnd[3], startEnd[2]), mode='nearest' ) xi = np.linspace(0, profile_length, len(zi)) if self.profilePlot is None or not self.profilePlot.exists: self.profilePlot = Plot(makeInteractive=True) else: self.profilePlot.clear() self.profilePlot.ax.plot(xi, zi, **kwargs) self.profilePlot.ax.set_xlabel('Distance (pixels)') self.profilePlot.ax.set_ylabel('Intensity') self.profilePlot.draw()
[docs] def setHomogPoint(self, binSize=1, points=None, **kwargs): """ Interactive tool to set homologous points. Right-click on a point then click 'save point' to append to the homologous points list. Parameters ---------- binSize : int, optional Binning applied to image, if applicable. points : numpy.ndarray, optional Array of (x,y) homologous points to set explicitly. kwargs : dict, optional Keyword arguments passed to :func:`defdap.base.Map.plotHomog` """ if points is None: plot = self.plotHomog(makeInteractive=True, **kwargs) # Plot stored homogo points if there are any if len(self.homogPoints) > 0: homogPoints = np.array(self.homogPoints) * binSize plot.addPoints(homogPoints[:, 0], homogPoints[:, 1], c='y', s=60) else: # add empty points layer to update later plot.addPoints([None], [None], c='y', s=60) # add empty points layer for current selected point plot.addPoints([None], [None], c='w', s=60, marker='x') plot.addEventHandler('button_press_event', self.clickHomog) plot.addEventHandler('key_press_event', self.keyHomog) plot.addButton("Save point", lambda e, p: self.clickSaveHomog(e, p, binSize), color="0.85", hovercolor="blue") else: self.homogPoints = points
[docs] def clickHomog(self, event, plot): """Event handler for capturing position when clicking on a map. Parameters ---------- event : Click event. plot : defdap.plotting.MapPlot Plot to monitor. """ # check if click was on the map if event.inaxes is not plot.ax: return # right mouse click or shift + left mouse click # shift click doesn't work in osx backend if (event.button == 3 or (event.button == 1 and event.key == 'shift')): plot.addPoints([int(event.xdata)], [int(event.ydata)], updateLayer=1)
[docs] def keyHomog(self, event, plot): """Event handler for moving position using keyboard after clicking on a map. Parameters ---------- event : Keypress event. plot : defdap.plotting.MapPlot Plot to monitor. """ keys = ['left', 'right', 'up', 'down'] key = event.key.split('+') if key[-1] in keys: # get the selected point points = plot.imgLayers[plot.pointsLayerIDs[1]] selPoint = points.get_offsets()[0] # check if a point is selected if selPoint[0] is not None and selPoint[1] is not None: # print(event.key) move = 1 if len(key) == 2 and key[0] == 'shift': move = 10 if key[-1] == keys[0]: selPoint[0] -= move elif key[-1] == keys[1]: selPoint[0] += move elif key[-1] == keys[2]: selPoint[1] -= move elif key[-1] == keys[3]: selPoint[1] += move plot.addPoints([selPoint[0]], [selPoint[1]], updateLayer=1)
[docs] def clickSaveHomog(self, event, plot, binSize): """Append the selected point on the map to homogPoints. Parameters ---------- event : Button click event. plot : defdap.plotting.MapPlot Plot to monitor. binSize : int, optional Binning applied to image, if applicable. """ # get the selected point points = plot.imgLayers[plot.pointsLayerIDs[1]] selPoint = points.get_offsets()[0] # Check if a point is selected if selPoint[0] is not None and selPoint[1] is not None: # remove selected point from plot plot.addPoints([None], [None], updateLayer=1) # then scale and add to homog points list selPoint = tuple((selPoint / binSize).round().astype(int)) self.homogPoints.append(selPoint) # update the plotted homog points homogPoints = np.array(self.homogPoints) * binSize plot.addPoints(homogPoints[:, 0], homogPoints[:, 1], updateLayer=0)
[docs] def updateHomogPoint(self, homogID, newPoint=None, delta=None): """ Update a homog point by either over writing it with a new point or incrementing the current values. Parameters ---------- homogID : int ID (place in list) of point to update or -1 for all. newPoint : tuple, optional (x, y) coordinates of new point. delta : tuple, optional Increments to current point (dx, dy). """ if type(homogID) is not int: raise Exception("homogID must be an integer.") if homogID >= len(self.homogPoints): raise Exception("homogID is out of range.") # Update all points if homogID < 0: for i in range(len(self.homogPoints)): self.updateHomogPoint(homogID=i, delta=delta) # Update a single point else: # overwrite point if newPoint is not None: if type(newPoint) is not tuple and len(newPoint) != 2: raise Exception("newPoint must be a 2 component tuple") # increment current point elif delta is not None: if type(delta) is not tuple and len(delta) != 2: raise Exception("delta must be a 2 component tuple") newPoint = list(self.homogPoints[homogID]) newPoint[0] += delta[0] newPoint[1] += delta[1] newPoint = tuple(newPoint) self.homogPoints[homogID] = newPoint
[docs] @reportProgress("constructing neighbour network") def buildNeighbourNetwork(self): """Construct a list of neighbours """ # create network nn = nx.Graph() nn.add_nodes_from(self.grainList) yLocs, xLocs = np.nonzero(self.boundaries) totalPoints = len(xLocs) for iPoint, (x, y) in enumerate(zip(xLocs, yLocs)): # report progress yield iPoint / totalPoints if (x == 0 or y == 0 or x == self.grains.shape[1] - 1 or y == self.grains.shape[0] - 1): # exclude boundary pixels of map continue # use 4 nearest neighbour points as potential neighbour grains # (this maybe needs changing considering the position of # boundary pixels relative to the actual edges) # use sets as they do not allow duplicate elements # minus 1 on all as the grain image starts labeling at 1 neighbours = { self.grains[y + 1, x] - 1, self.grains[y - 1, x] - 1, self.grains[y, x + 1] - 1, self.grains[y, x - 1] - 1 } # neighbours = set(neighbours) # remove boundary points (-2) and points in small # grains (-3) (Normally -1 and -2) neighbours.discard(-2) neighbours.discard(-3) neighbours = tuple(neighbours) nunNeig = len(neighbours) if nunNeig <= 1: continue for i in range(nunNeig): for j in range(i + 1, nunNeig): # Add to network grain = self[neighbours[i]] neiGrain = self[neighbours[j]] try: # look up boundary nn[grain][neiGrain] except KeyError: # neighbour relation doesn't exist so add it nn.add_edge(grain, neiGrain) self.neighbourNetwork = nn
[docs] def displayNeighbours(self, **kwargs): return self.locateGrainID( clickEvent=self.clickGrainNeighbours, **kwargs )
[docs] def clickGrainNeighbours(self, event, plot): """Event handler to capture clicking and show neighbours of selected grain. Parameters ---------- event : Click event. plot : defdap.plotting.MapPlot Plot to monitor. """ # check if click was on the map if event.inaxes is not plot.ax: return # grain id of selected grain grainId = int(self.grains[int(event.ydata), int(event.xdata)] - 1) if grainId < 0: return self.currGrainId = grainId grain = self[grainId] # find first and second nearest neighbours firstNeighbours = list(self.neighbourNetwork.neighbors(grain)) highlightGrains = [grain] + firstNeighbours secondNeighbours = [] for firstNeighbour in firstNeighbours: trialSecondNeighbours = list( self.neighbourNetwork.neighbors(firstNeighbour) ) for secondNeighbour in trialSecondNeighbours: if (secondNeighbour not in highlightGrains and secondNeighbour not in secondNeighbours): secondNeighbours.append(secondNeighbour) highlightGrains.extend(secondNeighbours) highlightGrains = [grain.grainID for grain in highlightGrains] highlightColours = ['white'] highlightColours.extend(['yellow'] * len(firstNeighbours)) highlightColours.append('green') # update the grain highlights layer in the plot plot.addGrainHighlights(highlightGrains, grainColours=highlightColours)
@property def proxigram(self): """Proxigram for a map. Returns ------- numpy.ndarray Distance from a grain boundary at each point in map. """ self.calcProxigram(forceCalc=False) return self.proxigramArr
[docs] @reportProgress("calculating proxigram") def calcProxigram(self, numTrials=500, forceCalc=True): """Calculate distance from a grain boundary at each point in map. Parameters ---------- numTrials : int, optional number of trials. forceCalc : bool, optional Force calculation even is proxigramArr is populated. """ if self.proxigramArr is not None and not forceCalc: return proxBoundaries = np.copy(self.boundaries) proxShape = proxBoundaries.shape # ebsd boundary arrays have extra boundary along right and # bottom edge. These need to be removed right edge if np.all(proxBoundaries[:, -1] == -1): proxBoundaries[:, -1] = proxBoundaries[:, -2] # bottom edge if np.all(proxBoundaries[-1, :] == -1): proxBoundaries[-1, :] = proxBoundaries[-2, :] # create list of positions of each boundary point indexBoundaries = [] for index, value in np.ndenumerate(proxBoundaries): if value == -1: indexBoundaries.append(index) # add 0.5 to boundary coordiantes as they are placed on the # bottom right edge pixels of grains indexBoundaries = np.array(indexBoundaries) + 0.5 # array of x and y coordinate of each pixel in the map coords = np.zeros((2, proxShape[0], proxShape[1]), dtype=float) coords[0], coords[1] = np.meshgrid( range(proxShape[0]), range(proxShape[1]), indexing='ij' ) # array to store trial distance from each boundary point trialDistances = np.full((numTrials + 1, proxShape[0], proxShape[1]), 1000, dtype=float) # loop over each boundary point (p) and calculate distance from # p to all points in the map store minimum once numTrails have # been made and start a new batch of trials numBoundaryPoints = len(indexBoundaries) j = 1 for i, indexBoundary in enumerate(indexBoundaries): trialDistances[j] = np.sqrt((coords[0] - indexBoundary[0])**2 + (coords[1] - indexBoundary[1])**2) if j == numTrials: # find current minimum distances and store trialDistances[0] = trialDistances.min(axis=0) j = 0 # report progress yield i / numBoundaryPoints j += 1 # find final minimum distances to a boundary self.proxigramArr = trialDistances.min(axis=0) trialDistances = None
[docs] def calcGrainAv(self, mapData, grainIds=-1): """Calculate grain average of any DIC map data. Parameters ---------- mapData : numpy.ndarray Array of map data to grain average. This must be cropped! grainIds : list, optional grainIDs to perform operation on, set to -1 for all grains. Returns ------- numpy.ndarray Array containing the grain average values. """ # Check that grains have been detected in the map self.checkGrainsDetected() if type(grainIds) is int and grainIds == -1: grainIds = range(len(self)) grainAvData = np.zeros(len(grainIds)) for i, grainId in enumerate(grainIds): grain = self[grainId] grainData = grain.grainData(mapData) grainAvData[i] = grainData.mean() return grainAvData
[docs] def grainDataToMapData(self, grainData, grainIds=-1, bg=0): """Create a map array with each grain filled with the given values. Parameters ---------- grainData : list or numpy.ndarray Grain values. This can be a single value per grain or RGB values. grainIds : list of int or int, optional IDs of grains to plot for. Use -1 for all grains in the map. bg : int or real, optional Value to fill the background with. Returns ------- grainMap: numpy.ndarray Array filled with grain data values """ # Check that grains have been detected in the map self.checkGrainsDetected() if type(grainIds) is int: if grainIds == -1: grainIds = range(len(self)) else: grainIds = [grainIds] grainData = np.array(grainData) if grainData.shape[0] != len(grainIds): raise ValueError("The length of supplied grain data does not" "match the number of grains.") if len(grainData.shape) == 1: mapShape = [self.yDim, self.xDim] elif len(grainData.shape) == 2 and grainData.shape[1] == 3: mapShape = [self.yDim, self.xDim, 3] else: raise ValueError("The grain data supplied must be either a" "single value or RGB values per grain.") grainMap = np.full(mapShape, bg, dtype=grainData.dtype) for grainId, grainValue in zip(grainIds, grainData): for coord in self[grainId].coordList: grainMap[coord[1], coord[0]] = grainValue return grainMap
[docs] def plotGrainDataMap( self, mapData=None, grainData=None, grainIds=-1, bg=0, **kwargs ): """Plot a grain map with grains coloured by given data. The data can be provided as a list of values per grain or as a map which a grain average will be applied. Parameters ---------- mapData : numpy.ndarray, optional Array of map data. This must be cropped! Either mapData or grainData must be supplied. grainData : list or np.array, optional Grain values. This an be a single value per grain or RGB values. You must supply either mapData or grainData. grainIds: list of int or int, optional IDs of grains to plot for. Use -1 for all grains in the map. bg: int or real, optional Value to fill the background with. kwargs : dict, optional Keyword arguments passed to :func:`defdap.plotting.MapPlot.create` Returns ------- plot: defdap.plotting.MapPlot Plot object created """ # Set default plot parameters then update with any input plotParams = {} plotParams.update(kwargs) if grainData is None: if mapData is None: raise ValueError("Either 'mapData' or 'grainData' must " "be supplied.") else: grainData = self.calcGrainAv(mapData, grainIds=grainIds) grainMap = self.grainDataToMapData(grainData, grainIds=grainIds, bg=bg) plot = MapPlot.create(self, grainMap, **plotParams) return plot
[docs] def plotGrainDataIPF( self, direction, mapData=None, grainData=None, grainIds=-1, **kwargs ): """ Plot IPF of grain reference (average) orientations with points coloured by grain average values from map data. Parameters ---------- direction : numpy.ndarray Vector of reference direction for the IPF. mapData : numpy.ndarray Array of map data. This must be cropped! Either mapData or grainData must be supplied. grainData : list or np.array, optional Grain values. This an be a single value per grain or RGB values. You must supply either mapData or grainData. grainIds: list of int or int, optional IDs of grains to plot for. Use -1 for all grains in the map. kwargs : dict, optional Keyword arguments passed to :func:`defdap.quat.Quat.plotIPF` """ # Set default plot parameters then update with any input plotParams = {} plotParams.update(kwargs) if grainData is None: if mapData is None: raise ValueError("Either 'mapData' or 'grainData' must " "be supplied.") else: grainData = self.calcGrainAv(mapData, grainIds=grainIds) # Check that grains have been detected in the map self.checkGrainsDetected() if type(grainIds) is int and grainIds == -1: grainIds = range(len(self)) if len(grainData) != len(grainIds): raise Exception("Must be 1 value for each grain in grainData.") grainOri = np.empty(len(grainIds), dtype=Quat) for i, grainId in enumerate(grainIds): grain = self[grainId] grainOri[i] = grain.refOri plot = Quat.plotIPF(grainOri, direction, self.crystalSym, c=grainData, **plotParams) return plot
[docs]class Grain(object): """ Base class for a grain. Attributes ---------- grainID : int ownerMap : defdap.base.Map coordList : list of tuples """ def __init__(self, grainID, ownerMap): # list of coords stored as tuples (x, y). These are coords in a # cropped image if crop exists. self.grainID = grainID self.ownerMap = ownerMap self.coordList = [] def __len__(self): return len(self.coordList) def __str__(self): return f"Grain(ID={self.grainID})" @property def extremeCoords(self): """Coordinates of the bounding box for a grain. Returns ------- int, int, int, int minimum x, minimum y, maximum x, maximum y. """ coords = np.array(self.coordList, dtype=int) x0, y0 = coords.min(axis=0) xmax, ymax = coords.max(axis=0) return x0, y0, xmax, ymax
[docs] def centreCoords(self, centreType="box", grainCoords=True): """ Calculates the centre of the grain, either as the centre of the bounding box or the grains centre of mass. Parameters ---------- centreType : str, optional, {'box', 'com'} Set how to calculate the centre. Either 'box' for centre of bounding box or 'com' for centre of mass. Default is 'box'. grainCoords : bool, optional If set True the centre is returned in the grain coordinates otherwise in the map coordinates. Defaults is grain. Returns ------- int, int Coordinates of centre of grain. """ x0, y0, xmax, ymax = self.extremeCoords if centreType == "box": xCentre = round((xmax + x0) / 2) yCentre = round((ymax + y0) / 2) elif centreType == "com": xCentre, yCentre = np.array(self.coordList).mean(axis=0).round() else: raise ValueError("centreType must be box or com") if grainCoords: xCentre -= x0 yCentre -= y0 return int(xCentre), int(yCentre)
[docs] def grainOutline(self, bg=np.nan, fg=0): """Generate an array of the grain outline. Parameters ---------- bg : int Value for points not within grain. fg : int Value for points within grain. Returns ------- numpy.ndarray Bounding box for grain with :obj:`~numpy.nan` outside the grain and given number within. """ x0, y0, xmax, ymax = self.extremeCoords # initialise array with nans so area not in grain displays white outline = np.full((ymax - y0 + 1, xmax - x0 + 1), bg, dtype=int) for coord in self.coordList: outline[coord[1] - y0, coord[0] - x0] = fg return outline
[docs] def plotOutline(self, ax=None, plotScaleBar=False, **kwargs): """Plot the outline of the grain. Parameters ---------- ax : matplotlib.axes.Axes axis to plot on, if not provided the current active axis is used. plotScaleBar : bool plots the scale bar on the grain if true. kwargs : dict keyword arguments passed to :func:`defdap.plotting.GrainPlot.addMap` Returns ------- defdap.plotting.GrainPlot """ plot = plotting.GrainPlot(self, ax=ax) plot.addMap(self.grainOutline(), **kwargs) if plotScaleBar: plot.addScaleBar() return plot
[docs] def grainData(self, mapData): """Extract this grains data from the given map data. Parameters ---------- mapData : numpy.ndarray Array of map data. This must be cropped! Returns ------- numpy.ndarray Array containing this grains values from the given map data. """ grainData = np.zeros(len(self), dtype=mapData.dtype) for i, coord in enumerate(self.coordList): grainData[i] = mapData[coord[1], coord[0]] return grainData
[docs] def grainMapData(self, mapData=None, grainData=None, bg=np.nan): """Extract a single grain map from the given map data. Parameters ---------- mapData : numpy.ndarray Array of map data. This must be cropped! Either this or 'grainData' must be supplied and 'grainData' takes precedence. grainData : numpy.ndarray Array of data at each point in the grain. Either this or 'mapData' must be supplied and 'grainData' takes precedence. bg : various, optional Value to fill the background with. Must be same dtype as input array. Returns ------- numpy.ndarray Grain map extracted from given data. """ if grainData is None: if mapData is None: raise ValueError("Either 'mapData' or 'grainData' must " "be supplied.") else: grainData = self.grainData(mapData) x0, y0, xmax, ymax = self.extremeCoords grainMapData = np.full((ymax - y0 + 1, xmax - x0 + 1), bg, dtype=type(grainData[0])) for coord, data in zip(self.coordList, grainData): grainMapData[coord[1] - y0, coord[0] - x0] = data return grainMapData
[docs] def grainMapDataCoarse(self, mapData=None, grainData=None, kernelSize=2, bg=np.nan): """ Create a coarsed data map of this grain only from the given map data. Data is coarsened using a kernel at each pixel in the grain using only data in this grain. Parameters ---------- mapData : numpy.ndarray Array of map data. This must be cropped! Either this or 'grainData' must be supplied and 'grainData' takes precedence. grainData : numpy.ndarray List of data at each point in the grain. Either this or 'mapData' must be supplied and 'grainData' takes precedence. kernelSize : int, optional Size of kernel as the number of pixels to dilate by i.e 1 gives a 3x3 kernel. bg : various, optional Value to fill the background with. Must be same dtype as input array. Returns ------- numpy.ndarray Map of this grains coarsened data. """ grainMapData = self.grainMapData(mapData=mapData, grainData=grainData) grainMapDataCoarse = np.full_like(grainMapData, np.nan) for i, j in np.ndindex(grainMapData.shape): if np.isnan(grainMapData[i, j]): grainMapDataCoarse[i, j] = bg else: coarseValue = 0 if i - kernelSize >= 0: yLow = i - kernelSize else: yLow = 0 if i + kernelSize + 1 <= grainMapData.shape[0]: yHigh = i + kernelSize + 1 else: yHigh = grainMapData.shape[0] if j - kernelSize >= 0: xLow = j - kernelSize else: xLow = 0 if j + kernelSize + 1 <= grainMapData.shape[1]: xHigh = j + kernelSize + 1 else: xHigh = grainMapData.shape[1] numPoints = 0 for k in range(yLow, yHigh): for l in range(xLow, xHigh): if not np.isnan(grainMapData[k, l]): coarseValue += grainMapData[k, l] numPoints += 1 if numPoints > 0: grainMapDataCoarse[i, j] = coarseValue / numPoints else: grainMapDataCoarse[i, j] = np.nan return grainMapDataCoarse
[docs] def plotGrainData(self, mapData=None, grainData=None, **kwargs): """ Plot a map of this grain from the given map data. Parameters ---------- mapData : numpy.ndarray Array of map data. This must be cropped! Either this or 'grainData' must be supplied and 'grainData' takes precedence. grainData : numpy.ndarray List of data at each point in the grain. Either this or 'mapData' must be supplied and 'grainData' takes precedence. kwargs : dict, optional Keyword arguments passed to :func:`defdap.plotting.GrainPlot.create` """ # Set default plot parameters then update with any input plotParams = {} plotParams.update(kwargs) grainMapData = self.grainMapData(mapData=mapData, grainData=grainData) plot = GrainPlot.create(self, grainMapData, **plotParams) return plot