diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c773b3c..88c425f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v2 diff --git a/defdap/_accelerated.py b/defdap/_accelerated.py new file mode 100644 index 0000000..5adf189 --- /dev/null +++ b/defdap/_accelerated.py @@ -0,0 +1,148 @@ +from numba import njit +import numpy as np + + +@njit +def find_first(arr): + for i in range(len(arr)): + if arr[i]: + return i + + +@njit +def flood_fill(seed, index, points_remaining, grains, boundary_x, boundary_y, + added_coords): + """Flood fill algorithm that uses the x and y boundary arrays to + fill a connected area around the seed point. The points are inserted + into a grain object and the grain map array is updated. + + Parameters + ---------- + seed : tuple of 2 int + Seed point x for flood fill + index : int + Value to fill in grain map + points_remaining : numpy.ndarray + Boolean map of the points that have not been assigned a grain yet + + Returns + ------- + grain : defdap.ebsd.Grain + New grain object with points added + """ + x, y = seed + grains[y, x] = index + points_remaining[y, x] = False + edge = [seed] + added_coords[0] = seed + npoints = 1 + + while edge: + x, y = edge.pop() + moves = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)] + # get rid of any that go out of the map area + if x <= 0: + moves.pop(1) + elif x > grains.shape[1] - 2: + moves.pop(0) + if y <= 0: + moves.pop(-1) + elif y > grains.shape[0] - 2: + moves.pop(-2) + + for (s, t) in moves: + if grains[t, s] > 0: + continue + + add_point = False + + if t == y: + # moving horizontally + if s > x: + # moving right + add_point = not boundary_x[y, x] + else: + # moving left + add_point = not boundary_x[t, s] + else: + # moving vertically + if t > y: + # moving down + add_point = not boundary_y[y, x] + else: + # moving up + add_point = not boundary_y[t, s] + + if add_point: + added_coords[npoints] = s, t + grains[t, s] = index + points_remaining[t, s] = False + npoints += 1 + edge.append((s, t)) + + return added_coords[:npoints] + + +@njit +def flood_fill_dic(seed, index, points_remaining, grains, added_coords): + """Flood fill algorithm that uses the combined x and y boundary array + to fill a connected area around the seed point. The points are returned and + the grain map array is updated. + + Parameters + ---------- + seed : tuple of 2 int + Seed point x for flood fill + index : int + Value to fill in grain map + points_remaining : numpy.ndarray + Boolean map of the points remaining to assign a grain yet + grains : numpy.ndarray + added_coords : numpy.ndarray + Buffer for points in the grain + + Returns + ------- + numpy.ndarray + Flooded points (n, 2) + + """ + # add first point to the grain + x, y = seed + grains[y, x] = index + points_remaining[y, x] = False + edge = [seed] + added_coords[0] = seed + npoints = 1 + + while edge: + x, y = edge.pop() + + moves = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)] + # get rid of any that go out of the map area + if x <= 0: + moves.pop(1) + elif x >= grains.shape[1] - 1: + moves.pop(0) + if y <= 0: + moves.pop(-1) + elif y >= grains.shape[0] - 1: + moves.pop(-2) + + for (s, t) in moves: + add_point = False + + if grains[t, s] == 0: + add_point = True + edge.append((s, t)) + + elif grains[t, s] == -1 and (s > x or t > y): + add_point = True + + if add_point: + added_coords[npoints] = (s, t) + grains[t, s] = index + points_remaining[t, s] = False + npoints += 1 + + return added_coords[:npoints] diff --git a/defdap/base.py b/defdap/base.py index ca149c7..f419e29 100755 --- a/defdap/base.py +++ b/defdap/base.py @@ -793,17 +793,6 @@ def __len__(self): def __str__(self): return f"Grain(ID={self.grain_id})" - def add_point(self, point): - """Append a coordinate and a quat to a grain. - - Parameters - ---------- - point : tuple - (x,y) coordinate to append - - """ - self.data.point.append(point) - @property def extreme_coords(self): """Coordinates of the bounding box for a grain. @@ -814,12 +803,7 @@ def extreme_coords(self): minimum x, minimum y, maximum x, maximum y. """ - points = np.array(self.data.point, dtype=int) - - x0, y0 = points.min(axis=0) - xmax, ymax = points.max(axis=0) - - return x0, y0, xmax, ymax + return *self.data.point.min(axis=0), *self.data.point.max(axis=0) def centre_coords(self, centre_type="box", grain_coords=True): """ @@ -846,7 +830,7 @@ def centre_coords(self, centre_type="box", grain_coords=True): x_centre = round((xmax + x0) / 2) y_centre = round((ymax + y0) / 2) elif centre_type == "com": - x_centre, y_centre = np.array(self.data.point).mean(axis=0).round() + x_centre, y_centre = self.data.point.mean(axis=0).round() else: raise ValueError("centreType must be box or com") diff --git a/defdap/ebsd.py b/defdap/ebsd.py index ed93945..5d6919e 100755 --- a/defdap/ebsd.py +++ b/defdap/ebsd.py @@ -25,6 +25,7 @@ from defdap.file_writers import EBSDDataWriter from defdap.quat import Quat from defdap import base +from defdap._accelerated import flood_fill from defdap import defaults from defdap.plotting import MapPlot @@ -881,13 +882,17 @@ def find_grains(self, min_grain_size=10): # Loop until all points (except boundaries) have been assigned # to a grain or ignored i = 0 + coords_buffer = np.zeros((boundary_im_y.size, 2), dtype=np.intp) while found_point >= 0: # Flood fill first unknown point and return grain object seed = np.unravel_index(next_point, self.shape) - grain = self.flood_fill( + + grain = Grain(grain_index - 1, self, group_id) + grain.data.point = flood_fill( (seed[1], seed[0]), grain_index, points_left, grains, - boundary_im_x, boundary_im_y, group_id + boundary_im_x, boundary_im_y, coords_buffer ) + coords_buffer = coords_buffer[len(grain.data.point):] if len(grain) < min_grain_size: # if grain size less than minimum, ignore grain and set @@ -962,81 +967,6 @@ def plot_grain_map(self, **kwargs): return plot - def flood_fill(self, seed, index, points_left, grains, boundary_im_x, - boundary_im_y, group_id): - """Flood fill algorithm that uses the x and y boundary arrays to - fill a connected area around the seed point. The points are inserted - into a grain object and the grain map array is updated. - - Parameters - ---------- - seed : tuple of 2 int - Seed point x for flood fill - index : int - Value to fill in grain map - points_left : numpy.ndarray - Boolean map of the points that have not been assigned a grain yet - - Returns - ------- - grain : defdap.ebsd.Grain - New grain object with points added - """ - # create new grain - grain = Grain(index - 1, self, group_id) - - # add first point to the grain - x, y = seed - grain.add_point(seed) - grains[y, x] = index - points_left[y, x] = False - edge = [seed] - - while edge: - x, y = edge.pop(0) - - moves = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)] - # get rid of any that go out of the map area - if x <= 0: - moves.pop(1) - elif x >= self.shape[1] - 1: - moves.pop(0) - if y <= 0: - moves.pop(-1) - elif y >= self.shape[0] - 1: - moves.pop(-2) - - for (s, t) in moves: - if grains[t, s] > 0: - continue - - add_point = False - - if t == y: - # moving horizontally - if s > x: - # moving right - add_point = not boundary_im_x[y, x] - else: - # moving left - add_point = not boundary_im_x[t, s] - else: - # moving vertically - if t > y: - # moving down - add_point = not boundary_im_y[y, x] - else: - # moving up - add_point = not boundary_im_y[t, s] - - if add_point: - grain.add_point((s, t)) - grains[t, s] = index - points_left[t, s] = False - edge.append((s, t)) - - return grain - @report_progress("calculating grain mean orientations") def calc_grain_av_oris(self): """Calculate the average orientation of grains. diff --git a/defdap/hrdic.py b/defdap/hrdic.py index 8a1c852..d13d6fc 100755 --- a/defdap/hrdic.py +++ b/defdap/hrdic.py @@ -18,12 +18,14 @@ import inspect from skimage import transform as tf +from skimage import measure from scipy.stats import mode from scipy.ndimage import binary_dilation import peakutils +from defdap._accelerated import flood_fill_dic from defdap.utils import Datastore from defdap.file_readers import DICDataLoader from defdap import base @@ -618,6 +620,8 @@ def find_grains(self, algorithm=None, min_grain_size=10): new = np.concatenate((neg_vals, np.arange(1, len(ebsd_grain_ids) + 1))) index = np.digitize(grains.ravel(), old, right=True) grains = new[index].reshape(self.shape) + grainprops = measure.regionprops(grains) + props_dict = {prop.label: prop for prop in grainprops} for dic_grain_id, ebsd_grain_id in enumerate(ebsd_grain_ids): yield dic_grain_id / len(ebsd_grain_ids) @@ -626,8 +630,8 @@ def find_grains(self, algorithm=None, min_grain_size=10): grain = Grain(dic_grain_id, self, group_id) # Find (x,y) coordinates and corresponding max shears of grain - coords = np.argwhere(grains == dic_grain_id + 1) # (y,x) - grain.data.point = [(x, y) for y, x in coords] + coords = props_dict[dic_grain_id + 1].coords # (y, x) + grain.data.point = np.flip(coords, axis=1) # (x, y) # Assign EBSD grain ID to DIC grain and increment grain list grain.ebsd_grain = self.ebsd_map[ebsd_grain_id - 1] @@ -640,6 +644,7 @@ def find_grains(self, algorithm=None, min_grain_size=10): # List of points where no grain has been set yet points_left = grains == 0 + coords_buffer = np.zeros((points_left.size, 2), dtype=np.intp) total_points = points_left.sum() found_point = 0 next_point = points_left.tobytes().find(b'\x01') @@ -652,9 +657,13 @@ def find_grains(self, algorithm=None, min_grain_size=10): while found_point >= 0: # Flood fill first unknown point and return grain object seed = np.unravel_index(next_point, self.shape) - grain = self.flood_fill( - (seed[1], seed[0]), grain_index, points_left, grains, group_id + + grain = Grain(grain_index - 1, self, group_id) + grain.data.point = flood_fill_dic( + (seed[1], seed[0]), grain_index, points_left, + grains, coords_buffer ) + coords_buffer = coords_buffer[len(grain.data.point):] if len(grain) < min_grain_size: # if grain size less than minimum, ignore grain and set @@ -711,67 +720,6 @@ def find_grains(self, algorithm=None, min_grain_size=10): self._grains = grain_list return grains - def flood_fill(self, seed, index, points_left, grains, group_id): - """Flood fill algorithm that uses the combined x and y boundary array - to fill a connected area around the seed point. The points are inserted - into a grain object and the grain map array is updated. - - Parameters - ---------- - seed : tuple of 2 int - Seed point x for flood fill - index : int - Value to fill in grain map - points_left : numpy.ndarray - Boolean map of the points that have not been assigned a grain yet - - Returns - ------- - grain : defdap.hrdic.Grain - New grain object with points added - - """ - # create new grain - grain = Grain(index - 1, self, group_id) - - # add first point to the grain - x, y = seed - grain.add_point(seed) - grains[y, x] = index - points_left[y, x] = False - edge = [seed] - - while edge: - x, y = edge.pop(0) - - moves = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)] - # get rid of any that go out of the map area - if x <= 0: - moves.pop(1) - elif x >= self.shape[1] - 1: - moves.pop(0) - if y <= 0: - moves.pop(-1) - elif y >= self.shape[0] - 1: - moves.pop(-2) - - for (s, t) in moves: - add_point = False - - if grains[t, s] == 0: - add_point = True - edge.append((s, t)) - - elif grains[t, s] == -1 and (s > x or t > y): - add_point = True - - if add_point: - grain.add_point((s, t)) - grains[t, s] = index - points_left[t, s] = False - - return grain - def grain_inspector(self, vmax=0.1, correction_angle=0, rdr_line_length=3): """Run the grain inspector interactive tool. diff --git a/setup.py b/setup.py index 06d2323..af67775 100644 --- a/setup.py +++ b/setup.py @@ -41,17 +41,17 @@ def get_version(): 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Framework :: IPython', 'Framework :: Jupyter', 'Framework :: Matplotlib' ], packages=find_packages(exclude=['tests']), package_data={'defdap': ['slip_systems/*.txt']}, - python_requires='>=3.6', + python_requires='>=3.8', install_requires=[ 'scipy>=1.9', 'numpy', @@ -61,11 +61,14 @@ def get_version(): 'peakutils', 'matplotlib_scalebar', 'networkx', + 'numba', ], extras_require={ 'testing': ['pytest', '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', 'ipykernel', 'pandoc', 'ipympl'] + 'docs': [ + 'sphinx==5.0.2', 'sphinx_rtd_theme==0.5.0', + 'sphinx_autodoc_typehints==1.11.1', 'nbsphinx==0.9.3', + 'ipykernel', 'pandoc', 'ipympl' + ] } - ) diff --git a/tests/data/expected_output/hrdic_grain_boundaries_5deg.npz b/tests/data/expected_output/hrdic_grain_boundaries_5deg.npz new file mode 100644 index 0000000..4535de3 Binary files /dev/null and b/tests/data/expected_output/hrdic_grain_boundaries_5deg.npz differ diff --git a/tests/data/expected_output/hrdic_grains_floodfill_0.npz b/tests/data/expected_output/hrdic_grains_floodfill_0.npz new file mode 100644 index 0000000..2a23636 Binary files /dev/null and b/tests/data/expected_output/hrdic_grains_floodfill_0.npz differ diff --git a/tests/data/expected_output/hrdic_grains_floodfill_10.npz b/tests/data/expected_output/hrdic_grains_floodfill_10.npz new file mode 100644 index 0000000..1b5492e Binary files /dev/null and b/tests/data/expected_output/hrdic_grains_floodfill_10.npz differ diff --git a/tests/data/expected_output/hrdic_grains_floodfill_100.npz b/tests/data/expected_output/hrdic_grains_floodfill_100.npz new file mode 100644 index 0000000..79bfc8f Binary files /dev/null and b/tests/data/expected_output/hrdic_grains_floodfill_100.npz differ diff --git a/tests/data/expected_output/hrdic_grains_warped-old.npz b/tests/data/expected_output/hrdic_grains_warped-old.npz new file mode 100644 index 0000000..9556a24 Binary files /dev/null and b/tests/data/expected_output/hrdic_grains_warped-old.npz differ diff --git a/tests/data/expected_output/hrdic_grains_warped.npz b/tests/data/expected_output/hrdic_grains_warped.npz new file mode 100644 index 0000000..c7268cc Binary files /dev/null and b/tests/data/expected_output/hrdic_grains_warped.npz differ diff --git a/tests/test_ebsd.py b/tests/test_ebsd.py index 2772361..c07a153 100644 --- a/tests/test_ebsd.py +++ b/tests/test_ebsd.py @@ -1,7 +1,5 @@ import pytest -from pytest import approx from unittest.mock import Mock, MagicMock -from functools import partial import numpy as np import defdap.ebsd as ebsd @@ -102,10 +100,6 @@ def mock_map(good_quat_array, good_phase_array, good_symmetries): mock_crystal_structure.symmetries = good_symmetries mock_phase = Mock(spec=crystal.Phase) mock_phase.crystal_structure = mock_crystal_structure - - # mock_phase = Mock(spec=crystal.Phase) - # mock_phase.crystal_structure = crystal.crystalStructures['cubic'] - mock_map.primary_phase = mock_phase return mock_map @@ -153,10 +147,8 @@ def mock_map(good_grain_boundaries, good_phase_array): mock_datastore.phase = good_phase_array mock_datastore.grain_boundaries = good_grain_boundaries mock_datastore.generate_id = Mock(return_value=1) - # mock_datastore.__iter__.return_value = [] mock_map.data = mock_datastore mock_map.shape = good_phase_array.shape - mock_map.flood_fill = partial(ebsd.Map.flood_fill, mock_map) mock_map.num_phases = 1 mock_map.phases = [Mock(crystal.Phase)] @@ -223,11 +215,11 @@ def test_grain_points(mock_map, min_grain_size): f'{EXPECTED_RESULTS_DIR}/ebsd_grains_5deg_{min_grain_size}.npz' )['grains'] + # transform both to set of tuples so order of points is ignored for i in range(expected_grains.max()): + expected_point = set(zip(*np.nonzero(expected_grains == i+1)[::-1])) - expected_point = zip(*np.nonzero(expected_grains == i+1)[::-1]) - - assert set(result[i].data.point) == set(expected_point) + assert set([(*r, ) for r in result[i].data.point]) == expected_point ''' Functions left to test diff --git a/tests/test_hrdic.py b/tests/test_hrdic.py index 1fcec0d..29a0464 100644 --- a/tests/test_hrdic.py +++ b/tests/test_hrdic.py @@ -1,9 +1,8 @@ import pytest -from pytest import approx from unittest.mock import Mock, MagicMock -from functools import partial import numpy as np + import defdap.ebsd as ebsd import defdap.hrdic as hrdic from defdap.utils import Datastore @@ -15,12 +14,40 @@ @pytest.fixture(scope="module") -def good_warped_grains(): +def good_grain_boundaries(): + expected = np.load( + f'{EXPECTED_RESULTS_DIR}/hrdic_grain_boundaries_5deg.npz' + ) + mock_map = Mock(spec=hrdic.Map) + mock_map.shape = (200, 300) + return hrdic.BoundarySet( + mock_map, + [tuple(row) for row in expected['points']], + None + ) + + +@pytest.fixture(scope="module") +def good_warped_ebsd_grains(): return np.load( f'{EXPECTED_RESULTS_DIR}/ebsd_grains_warped_5deg_0.npz' )['grains'] +@pytest.fixture(scope="module") +def good_warped_dic_grains(): + return np.load( + f'{EXPECTED_RESULTS_DIR}/hrdic_grains_warped.npz' + )['grains'] + + +@pytest.fixture(scope="module") +def good_ebsd_grains(): + return np.load( + f'{EXPECTED_RESULTS_DIR}/ebsd_grains_5deg_0.npz' + )['grains'] + + class TestMapFindGrains: # for warp depends on # check_ebsd_linked, warp_to_dic_frame, shape, ebsd_map @@ -30,26 +57,36 @@ class TestMapFindGrains: @staticmethod @pytest.fixture - def mock_map(good_warped_grains): + def mock_map(good_warped_ebsd_grains, good_grain_boundaries, + good_warped_dic_grains, good_ebsd_grains): # create stub object mock_map = Mock(spec=hrdic.Map) mock_map.check_ebsd_linked = Mock(return_value=True) - mock_map.warp_to_dic_frame = Mock(return_value=good_warped_grains) - mock_map.shape = good_warped_grains.shape + mock_map.warp_to_dic_frame = Mock(return_value=good_warped_ebsd_grains) + mock_map.shape = good_warped_ebsd_grains.shape mock_map.data = MagicMock(spec=Datastore) mock_map.data.generate_id = Mock(return_value=1) - mock_map.ebsd_map = MagicMock(spec=ebsd.Map) + mock_map.ebsd_map.__getitem__ = lambda self, k: k mock_map.ebsd_map.data = Mock(spec=Datastore) - mock_map.ebsd_map.data.grains = 'ebsd_grains' - # mock_map.flood_fill = partial(hrdic.Map.flood_fill, mock_map) + mock_map.data.grain_boundaries = good_grain_boundaries + + mock_map.experiment = Mock() + mock_map.experiment.warp_image = Mock( + return_value=good_warped_dic_grains + ) + mock_map.frame = Mock() + mock_map.ebsd_map.frame = Mock() + mock_map.ebsd_map.shape = good_warped_dic_grains.shape + mock_map.ebsd_map.data.grains = good_ebsd_grains return mock_map @staticmethod - def test_return_type(mock_map): - algorithm = 'warp' + @pytest.mark.parametrize('algorithm', ['warp', 'floodfill']) + def test_return_type(mock_map, algorithm): + # algorithm = 'warp' # run test and collect result result = hrdic.Map.find_grains(mock_map, algorithm=algorithm) @@ -58,14 +95,23 @@ def test_return_type(mock_map): assert result.dtype == np.int64 @staticmethod - def test_calc_warp(mock_map): - algorithm = 'warp' + @pytest.mark.parametrize('algorithm, min_grain_size', [ + ('warp', None), + ('floodfill', 0), + ('floodfill', 10), + ('floodfill', 100), + ]) + def test_calc_warp(mock_map, algorithm, min_grain_size): + # algorithm = 'warp' # run test and collect result - result = hrdic.Map.find_grains(mock_map, algorithm=algorithm) + result = hrdic.Map.find_grains( + mock_map, algorithm=algorithm, min_grain_size=min_grain_size + ) # load expected + min_grain_size = '' if min_grain_size is None else f'_{min_grain_size}' expected = np.load( - f'{EXPECTED_RESULTS_DIR}/hrdic_grains_{algorithm}.npz' + f'{EXPECTED_RESULTS_DIR}/hrdic_grains_{algorithm}{min_grain_size}.npz' )['grains'] assert np.alltrue(result == expected) @@ -81,7 +127,8 @@ def test_add_derivative(mock_map): mock_add_derivative.assert_called_once() @staticmethod - def test_grain_list_type(mock_map): + @pytest.mark.parametrize('algorithm', ['warp', 'floodfill']) + def test_grain_list_type(mock_map, algorithm): algorithm = 'warp' hrdic.Map.find_grains(mock_map, algorithm=algorithm) result = mock_map._grains @@ -91,30 +138,91 @@ def test_grain_list_type(mock_map): assert isinstance(g, hrdic.Grain) @staticmethod - def test_grain_list_size(mock_map): - algorithm = 'warp' - hrdic.Map.find_grains(mock_map, algorithm=algorithm) + @pytest.mark.parametrize('algorithm, expected', [ + ('warp', 111), ('floodfill', 80) + ]) + def test_grain_list_size(mock_map, algorithm, expected): + hrdic.Map.find_grains(mock_map, algorithm=algorithm, min_grain_size=10) result = mock_map._grains - assert len(result) == 111 + assert len(result) == expected @staticmethod - @pytest.mark.parametrize('min_grain_size', [0, 10, 100]) - def test_grain_points(mock_map, min_grain_size): - algorithm = 'warp' - hrdic.Map.find_grains(mock_map, algorithm=algorithm) + @pytest.mark.parametrize('algorithm, min_grain_size', [ + ('warp', None), + ('floodfill', 0), + ('floodfill', 10), + ('floodfill', 100), + ]) + def test_grain_points(mock_map, algorithm, min_grain_size): + hrdic.Map.find_grains( + mock_map, algorithm=algorithm, min_grain_size=min_grain_size + ) result = mock_map._grains # load expected + min_grain_size = '' if min_grain_size is None else f'_{min_grain_size}' expected_grains = np.load( - f'{EXPECTED_RESULTS_DIR}/hrdic_grains_{algorithm}.npz' + f'{EXPECTED_RESULTS_DIR}/hrdic_grains_{algorithm}{min_grain_size}.npz' )['grains'] + # transform both to set of tuples so order of points is ignored for i in range(expected_grains.max()): - expected_point = zip(*np.nonzero(expected_grains == i+1)[::-1]) + expected_point = set(zip(*np.nonzero(expected_grains == i+1)[::-1])) + + assert set([(*r, ) for r in result[i].data.point]) == expected_point + + @staticmethod + def test_call_warp_to_dic_frame(mock_map, good_ebsd_grains): + hrdic.Map.find_grains(mock_map, algorithm='warp') + + mock_map.warp_to_dic_frame.assert_called_once() + mock_map.warp_to_dic_frame.assert_called_with( + good_ebsd_grains, order=0, preserve_range=True + ) + + @staticmethod + def test_call_experiment_warp_image(mock_map, good_ebsd_grains): + hrdic.Map.find_grains(mock_map, algorithm='floodfill', min_grain_size=10) - assert set(result[i].data.point) == set(expected_point) + good_grains = np.load( + f'{EXPECTED_RESULTS_DIR}/hrdic_grains_floodfill_10.npz' + )['grains'] + + mock_map.experiment.warp_image.assert_called_once() + call_args = mock_map.experiment.warp_image.call_args + np.testing.assert_array_equal( + good_grains.astype(float), call_args[0][0] + ) + assert call_args[0][1] == mock_map.frame + assert call_args[0][2] == mock_map.ebsd_map.frame + assert call_args[1]['output_shape'] == mock_map.ebsd_map.shape + assert call_args[1]['order'] == 0 + + @staticmethod + @pytest.mark.parametrize('algorithm, expected', [ + ('warp', [ + 1, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 71, 72, 75, 77, 78, + 79, 80, 81, 82, 83, 84, 85, 86, 87, 89, 90, 91, 92, 93, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 105, 106, 107, 108, 109, 111, 112, 113, + 114, 115, 116, 117, 119, 120, 121, 122, 123, 125, 126 + ]), + ('floodfill', [ + 1, 13, 5, 6, 7, 8, 9, 11, 15, 14, 16, 17, 20, 21, 22, 18, 23, 25, + 19, 24, 28, 29, 32, 31, 30, 34, 35, 36, 33, 38, 39, 41, 40, 50, 44, + 39, 52, 47, 51, 48, 37, 57, 58, 65, 61, 62, 64, 72, 77, 79, 81, 80, + 75, 86, 90, 85, 87, 57, 91, 93, 92, 99, 99, 95, 97, 96, 100, 106, + 102, 97, 107, 108, 111, 112, 115, 117, 114, 120, 122, 123 + ]) + ]) + def test_grain_assigned_ebsd_grains(mock_map, algorithm, expected): + hrdic.Map.find_grains(mock_map, algorithm=algorithm, min_grain_size=10) + result = [g.ebsd_grain for g in mock_map._grains] + assert result == expected # methods to test # '_grad',