diff --git a/CHANGELOG.md b/CHANGELOG.md index 7109606..a1ab65a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ - Use example_notebook to generate a 'How To Use' page in the documentation ### Changed +- All functions and arguments are now in snake_case instead of CamelCase +- Cropping and masking are now performed upon access to data - Overhaul of data storage in the Map classes - RDR calculation `calcRDR` in grain inspector is faster and more robust - Improve formatting of grain inspector and RDR plot window @@ -25,6 +27,18 @@ - Remove `IPython` and `jupyter` as requirements +## 0.93.5 (20-11-2023) + +### Added +- Add more options for colouring lines + +### Fixed +- Fix bug with accessing slip systems in grain inspector +- Replace np.float with python float +- Remove in_place argument to skimage.morphology.remove_small_objects +- set_window_title has been moved from figure.canvas to figure.canvas.manager + + ## 0.93.5 (07-03-2022) ### Added diff --git a/defdap/base.py b/defdap/base.py index 5d54710..f8a59fd 100755 --- a/defdap/base.py +++ b/defdap/base.py @@ -56,7 +56,7 @@ def __init__(self, file_name, data_type=None, experiment=None, """ - self.data = Datastore(crop_func=self.crop) + self.data = Datastore(crop_func=self.crop, mask_func=self.mask) self.frame = frame if frame is not None else Frame() if increment is not None: self.increment = increment @@ -118,6 +118,9 @@ def y_dim(self): def crop(self, map_data, **kwargs): return map_data + + def mask(self, map_data, **kwargs): + return map_data def set_homog_point(self, **kwargs): self.frame.set_homog_point(self, **kwargs) diff --git a/defdap/ebsd.py b/defdap/ebsd.py index 3a6a8fa..8e3d2e5 100755 --- a/defdap/ebsd.py +++ b/defdap/ebsd.py @@ -73,7 +73,8 @@ class Map(base.Map): Nye_tensor : numpy.ndarray 3x3 Nye tensor at each point. Derived data: - Grain list data to map data from all grains + grain_data_to_map : numpy.ndarray + Grain list data to map data from all grains """ MAPNAME = 'ebsd' @@ -1156,9 +1157,9 @@ class Grain(base.Grain): (x, y) Generated data: GROD : numpy.ndarray - + Grain reference orientation distribution magnitude GROD_axis : numpy.ndarray - + Grain reference orientation distribution direction Derived data: Map data to list data from the map the grain is part of diff --git a/defdap/hrdic.py b/defdap/hrdic.py index a409d59..77bf7ba 100755 --- a/defdap/hrdic.py +++ b/defdap/hrdic.py @@ -118,9 +118,10 @@ def __init__(self, *args, **kwargs): self.bse_scale = None # size of pixels in pattern images self.crop_dists = np.array(((0, 0), (0, 0)), dtype=int) - ## TODO: cropping, have metadata to state if saved data is cropped, if - ## not cropped then crop on accesss. Maybe mark cropped data as invalid - ## if crop distances change + self.data.add_generator( + 'mask', self.calc_mask, unit='', type='map', order=0, + cropped=True, apply_mask=False + ) # Deformation gradient f = np.gradient(self.data.displacement, self.binning, axis=(1, 2)) @@ -132,9 +133,9 @@ def __init__(self, *args, **kwargs): plot_params={ 'plot_colour_bar': True, 'clabel': 'Deformation gradient', - } + }, + apply_mask=True ) - # Green strain e = 0.5 * (np.einsum('ki...,kj...->ij...', f, f)) e[0, 0] -= 0.5 @@ -144,9 +145,9 @@ def __init__(self, *args, **kwargs): plot_params={ 'plot_colour_bar': True, 'clabel': 'Green strain', - } + }, + apply_mask=True ) - # max shear component max_shear = np.sqrt(((e[0, 0] - e[1, 1]) / 2.) ** 2 + e[0, 1] ** 2) self.data.add( @@ -154,25 +155,24 @@ def __init__(self, *args, **kwargs): plot_params={ 'plot_colour_bar': True, 'clabel': 'Effective shear strain', - } + }, + apply_mask=True ) - # pattern image self.data.add_generator( 'pattern', self.load_pattern, unit='', type='map', order=0, - save=False, + save=False, apply_mask=False, plot_params={ 'cmap': 'gray' } ) - self.data.add_generator( 'grains', self.find_grains, unit='', type='map', order=0, - cropped=True + cropped=True, apply_mask=False ) - self.plot_default = lambda *args, **kwargs: self.plot_map(map_name='max_shear', - plot_gbs=True, *args, **kwargs + self.plot_default = lambda *args, **kwargs: self.plot_map( + map_name='max_shear', plot_gbs=True, *args, **kwargs ) self.homog_map_name = 'max_shear' @@ -444,26 +444,27 @@ def warp_to_dic_frame(self, map_data, **kwargs): **kwargs ) - # TODO: fix component stuff - def generate_threshold_mask(self, mask, dilation=0, preview=True): + def calc_mask(self, mask="unset_mask", dilation=0): """ - Generate a dilated mask, based on a boolean array and previews the appication of - this mask to the max shear map. + Generate a dilated mask, based on a boolean array. Parameters ---------- - mask: numpy.array(bool) - A boolean array where points to be removed are True + mask: numpy.array(bool) or str('unset_mask') + A boolean array where points to be removed are True. Set to string 'unset_mask' to disable masking. dilation: int, optional Number of pixels to dilate the mask by. Useful to remove anomalous points around masked values. No dilation applied if not specified. - preview: bool - If true, show the mask and preview the masked effective shear strain map. Examples ---------- - To remove data points in dic_map where `max_shear` is above 0.8, use: + + To disable masking: + >>> mask = 'unset_mask' + + To remove data points in dic_map where `max_shear` is above 0.8, use: + >>> mask = dic_map.data.max_shear > 0.8 To remove data points in dic_map where e11 is above 1 or less than -1, use: @@ -478,46 +479,32 @@ def generate_threshold_mask(self, mask, dilation=0, preview=True): see :func:`defdap.hrdic.load_corr_val_data` """ - self.mask = mask + if type(mask) == str and mask == "unset_mask": + return mask + + if not isinstance(mask, np.ndarray) or mask.shape != self.shape: + raise ValueError('The mask must be a numpy array the same shape as ' + 'the cropped map.') if dilation != 0: - self.mask = binary_dilation(self.mask, iterations=dilation) - - num_removed = np.sum(self.mask) - num_total = self.xdim * self.ydim - num_removed_crop = np.sum(self.crop(self.mask)) - num_total_crop = self.x_dim * self.y_dim - - print('Filtering will remove {0} \ {1} ({2:.3f} %) datapoints in map' - .format(num_removed, num_total, (num_removed / num_total) * 100)) - print( - 'Filtering will remove {0} \ {1} ({2:.3f} %) datapoints in cropped map' - .format(num_removed_crop, num_total_crop, - (num_removed_crop / num_total_crop * 100))) - - if preview == True: - plot1 = MapPlot.create(self, self.crop(self.mask), cmap='binary') - plot1.set_title('Removed datapoints in black') - plot2 = MapPlot.create(self, - self.crop( - np.where(self.mask == True, np.nan, - self.data.max_shear)), - plot_colour_bar='True', - clabel="Effective shear strain") - plot2.set_title('Effective shear strain preview') - print( - 'Use apply_threshold_mask function to apply this filtering to data') - - def apply_threshold_mask(self): - """ Apply mask to all DIC map data by setting masked values to nan. + mask = binary_dilation(mask, iterations=dilation) + num_removed = np.sum(mask) + num_total = self.shape[0] * self.shape[1] + frac_removed = num_removed / num_total * 100 + print(f'Masking will mask {num_removed} out of {num_total} ' + f'({frac_removed:.3f} %) datapoints in cropped map.') + + return mask + + def mask(self, map_data): + """ Values set to False in mask will be set to nan in map. """ - for comp in ('max_shear', - 'e11', 'e12', 'e22', - 'f11', 'f12', 'f21', 'e22', - 'x_map', 'y_map'): - # self.data[comp] = np.where(self.mask == True, np.nan, self.data[comp]) - self.data[comp][self.mask] = np.nan + if self.data.mask == 'unset_mask': + return map_data + else: + return np.ma.array(map_data, + mask=np.broadcast_to(self.data.mask, np.shape(map_data))) def set_pattern(self, img_path, window_size): """Set the path to the image of the pattern. diff --git a/defdap/utils.py b/defdap/utils.py index d1a3036..7ea8226 100644 --- a/defdap/utils.py +++ b/defdap/utils.py @@ -99,6 +99,7 @@ class Datastore(object): '_derivatives', '_group_id', '_crop_func', + '_mask_func' ] _been_to = None @@ -106,12 +107,13 @@ class Datastore(object): def generate_id(): return uuid4() - def __init__(self, group_id=None, crop_func=None): + def __init__(self, group_id=None, crop_func=None, mask_func=None): self._store = {} self._generators = {} self._derivatives = [] self._group_id = self.generate_id() if group_id is None else group_id self._crop_func = (lambda x, **kwargs: x) if crop_func is None else crop_func + self._mask_func = (lambda x, **kwargs: x) if mask_func is None else mask_func def __len__(self): """Number of data in the store, including data not yet generated.""" @@ -171,10 +173,12 @@ def __getitem__(self, key): # No generator found pass - if (attr == 'data' and self.get_metadata(key, 'type') == 'map' and - not self.get_metadata(key, 'cropped', False)): - binning = self.get_metadata(key, 'binning', 1) - val = self._crop_func(val, binning=binning) + if attr == 'data' and self.get_metadata(key, 'type') == 'map': + if not self.get_metadata(key, 'cropped', False): + binning = self.get_metadata(key, 'binning', 1) + val = self._crop_func(val, binning=binning) + if self.get_metadata(key, 'apply_mask', True): + val = self._mask_func(val) return val diff --git a/docs/source/papers.rst b/docs/source/papers.rst index 1e0902e..d71c9d6 100644 --- a/docs/source/papers.rst +++ b/docs/source/papers.rst @@ -6,6 +6,8 @@ Here is a list of papers which have used the DefDAP Python library. 2024 ------ +* `E. Nieto-Valeiras, A. Orozco-Caballero, M. Sarebanzadeh, J. Sun, J. LLorca. Analysis of slip transfer across grain boundaries in Ti via diffraction contrast tomography and high-resolution digital image correlation: When the geometrical criteria are not sufficient. Volume 175, April 2024, 103941. `_ + * `I.Alakiozidis, C.Hunt, R.Thomas, D.Lunt, A.D.Smith, M.Maric, Z.Shah, A.Ambard, P.Frankel. Quantifying cracking and strain localisation in a cold spray chromium coating on a zirconium alloy substrate under tensile loading at room temperature. Journal of Nuclear Materials. Apr 2024. 154899. `_ * `B.Poole, A.Marsh, D.Lunt, M.Gorley, C.Hamelin, C. Hardie, A.Harte. High-resolution strain mapping in a thermionic LaB6 scanning electron microscope. Strain. Feb 2024. `_ diff --git a/setup.py b/setup.py index af67775..4878f07 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ def get_version(): 'numba', ], extras_require={ - 'testing': ['pytest', 'coverage', 'pytest-cov', 'pytest_cases'], + 'testing': ['pytest<8', 'coverage', 'pytest-cov', 'pytest_cases'], 'docs': [ 'sphinx==5.0.2', 'sphinx_rtd_theme==0.5.0', 'sphinx_autodoc_typehints==1.11.1', 'nbsphinx==0.9.3',