Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve masking #122

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 4 additions & 1 deletion defdap/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions defdap/ebsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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

Expand Down
105 changes: 46 additions & 59 deletions defdap/hrdic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -144,35 +145,34 @@ 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(
'max_shear', max_shear, unit='', type='map', order=0,
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'

Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down
14 changes: 9 additions & 5 deletions defdap/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,21 @@ class Datastore(object):
'_derivatives',
'_group_id',
'_crop_func',
'_mask_func'
]
_been_to = None

@staticmethod
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."""
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions docs/source/papers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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. <https://doi.org/10.1016/j.ijplas.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. <https://doi.org/10.1016/j.jnucmat.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. <https://doi.org/10.1111/str.12472>`_
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading