import numpy as np
from skimage import transform as tf
from skimage import morphology as mph
[docs]class Experiment(object):
def __init__(self):
self.frame_relations = {}
self.increments = []
def __getitem__(self, key):
return self.increments[key]
[docs] def add_increment(self, **kwargs):
inc = Increment(self, **kwargs)
self.increments.append(inc)
return inc
[docs] def iter_over_maps(self, map_name):
for i, inc in enumerate(self.increments):
map_obj = inc.maps.get(map_name)
if map_obj is None:
continue
yield i, map_obj
[docs] def link_frames(self, frame_1, frame_2, transform_props):
self.frame_relations[(frame_1, frame_2)] = transform_props
[docs] def warp_image(self, map_data, frame_1, frame_2, crop=True, **kwargs):
"""Warps a map to the DIC frame.
Parameters
----------
map_data : numpy.ndarray
Data to warp.
crop : bool, optional
Crop to size of DIC map if true.
kwargs
All other arguments passed to :func:`skimage.transform.warp`.
Returns
----------
numpy.ndarray
Map (i.e. EBSD map data) warped to the DIC frame.
"""
transform = self.get_frame_transform(frame_2, frame_1)
if not crop and isinstance(transform, tf.AffineTransform):
# copy transform and change translation to give an extra
# 5% border to show the entire image after rotation/shearing
input_shape = np.array(map_data.shape)
transform = tf.AffineTransform(matrix=np.copy(transform.params))
transform.params[0:2, 2] = -0.05 * input_shape
output_shape = input_shape * 1.4 / transform.scale
kwargs['output_shape'] = output_shape.astype(int)
return tf.warp(map_data, transform, **kwargs)
[docs] def warp_lines(self, lines, frame_1, frame_2):
"""Warp a set of lines to the DIC reference frame.
Parameters
----------
lines : list of tuples
Lines to warp. Each line is represented as a tuple of start
and end coordinates (x, y).
Returns
-------
list of tuples
List of warped lines with same representation as input.
"""
# Transform
transform = self.get_frame_transform(frame_1, frame_2)
lines = transform(np.array(lines).reshape(-1, 2)).reshape(-1, 2, 2)
# Round to nearest
lines = np.round(lines - 0.5) + 0.5
lines = [(tuple(line[0]), tuple(line[1])) for line in lines]
return lines
[docs] def warp_points(self, points_img, frame_1, frame_2, **kwargs):
input_shape = np.array(points_img.shape)
points_img = self.warp_image(points_img, frame_1, frame_2, crop=False,
**kwargs)
points_img = mph.skeletonize(points_img > 0.1)
mph.remove_small_objects(points_img, min_size=10, connectivity=2,
out=points_img)
# remove 5% border if required
transform = self.get_frame_transform(frame_2, frame_1)
if isinstance(transform, tf.AffineTransform):
# the crop is defined in EBSD coords so need to transform it
crop = np.matmul(
np.linalg.inv(transform.params[0:2, 0:2]),
transform.params[0:2, 2] + 0.05*input_shape
)
crop = crop.round().astype(int)
points_img = points_img[crop[1]:crop[1] + kwargs['output_shape'][0],
crop[0]:crop[0] + kwargs['output_shape'][1]]
return zip(*points_img.transpose().nonzero())
[docs]class Increment(object):
# def __init__(self, experiment, **kwargs):
def __init__(self, experiment, **kwargs):
self.maps = {}
# ex: (name, map, frame)
# default behaviour for no frame, different frame for
# each EBSD map, initial increment frame for DIC maps
self.experiment = experiment
self.metadata = kwargs
[docs] def add_map(self, name, map_obj):
self.maps[name] = map_obj
[docs]class Frame(object):
def __init__(self):
# self.maps = []
self.homog_points = []
[docs] def set_homog_points(self, points):
"""
Parameters
----------
points : numpy.ndarray, optional
Array of (x,y) homologous points to set explicitly.
"""
self.homog_points = points
[docs] def set_homog_point(self, map_obj, map_name=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
----------
map_name : str, optional
Map data to plot for selecting points.
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 map_name is None:
map_name = map_obj.homog_map_name
binning = map_obj.data.get_metadata(map_name, 'binning', 1)
plot = map_obj.plot_map(map_name, make_interactive=True, **kwargs)
# Plot stored homog points if there are any
if len(self.homog_points) > 0:
homog_points = np.array(self.homog_points) * binning
plot.add_points(homog_points[:, 0], homog_points[:, 1], c='y', s=60)
else:
# add empty points layer to update later
plot.add_points([None], [None], c='y', s=60)
# add empty points layer for current selected point
plot.add_points([None], [None], c='w', s=60, marker='x')
plot.add_event_handler('button_press_event', self.homog_click)
plot.add_event_handler('key_press_event', self.homog_key)
plot.add_button("Save point",
lambda e, p: self.homog_click_save(e, p, binning),
color="0.85", hovercolor="blue")
return plot
[docs] @staticmethod
def homog_click(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.add_points([int(event.xdata)], [int(event.ydata)], update_layer=1)
[docs] @staticmethod
def homog_key(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.
"""
arrow_keys = ['left', 'right', 'up', 'down']
keys = event.key.split('+')
key = keys[-1]
if key not in arrow_keys:
return
# get the selected point
sel_point = plot.img_layers[plot.points_layer_ids[1]].get_offsets()[0]
if sel_point[0] is None or sel_point[1] is None:
return
move = 10 if len(keys) == 2 and keys[0] == 'shift' else 1
if key == arrow_keys[0]:
sel_point[0] -= move
elif key == arrow_keys[1]:
sel_point[0] += move
elif key == arrow_keys[2]:
sel_point[1] -= move
elif key == arrow_keys[3]:
sel_point[1] += move
plot.add_points([sel_point[0]], [sel_point[1]], update_layer=1)
[docs] def homog_click_save(self, event, plot, binning):
"""Append the selected point on the map to homogPoints.
Parameters
----------
event :
Button click event.
plot : defdap.plotting.MapPlot
Plot to monitor.
binning : int, optional
Binning applied to image, if applicable.
"""
# get the selected point
sel_point = plot.img_layers[plot.points_layer_ids[1]].get_offsets()[0]
if sel_point[0] is None or sel_point[1] is None:
return
# remove selected point from plot
plot.add_points([None], [None], update_layer=1)
# then scale and add to homog points list
sel_point = tuple((sel_point / binning).round().astype(int))
self.homog_points.append(sel_point)
# update the plotted homog points
homog_points = np.array(self.homog_points) * binning
plot.add_points(homog_points[:, 0], homog_points[:, 1], update_layer=0)
[docs] def update_homog_points(self, homog_idx, new_point=None, delta=None):
"""
Update a homog point by either over writing it with a new point or
incrementing the current values.
Parameters
----------
homog_idx : int
ID (place in list) of point to update or -1 for all.
new_point : tuple, optional
(x, y) coordinates of new point.
delta : tuple, optional
Increments to current point (dx, dy).
"""
if type(homog_idx) is not int:
raise Exception("homog_idx must be an integer.")
if homog_idx >= len(self.homog_points):
raise Exception("homog_idx is out of range.")
# Update all points
if homog_idx < 0:
for i in range(len(self.homog_points)):
self.update_homog_points(homog_idx=i, delta=delta)
return
# Update a single point
# overwrite point
if new_point is not None:
if type(new_point) is not tuple and len(new_point) != 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")
new_point = list(self.homog_points[homog_idx])
new_point[0] += delta[0]
new_point[1] += delta[1]
new_point = tuple(new_point)
self.homog_points[homog_idx] = new_point