def uniform_rotated_line_sample_surface(l, w, speed, sample_rate, time, angle=0., follow_edge=True): r""" Sample a surface area using a uniform rotated line pattern. The coordinates (in units of meters) resulting from sampling an area of size `l` times `w` using uniform rotated line pattern are determined. The scanned path is determined from the probe `speed` and the scan `time`. Parameters ---------- l : float The length of the area to scan in units of meters. w : float The width of the area to scan in units of meters. speed : float The probe speed in units of meters/second. sample_rate : float The sample rate in units of Hertz. time : float The scan time in units of seconds. angle : float The angle measured in radians by which the uniform lines are rotated (the default is 0.0 resulting in a pattern identical to that of uniform_line_sample_image). follow_edge: bool A flag indicating whether or not the pattern foolows the edges of the rectangular area in-between lines (the default is True). Returns ------- coords : ndarray The coordinates of the samples arranged into a 2D array, such that each row is a coordinate pair (x, y). Notes ----- The orientation of the coordinate system is such that the width `w` is measured along the x-axis whereas the length `l` is measured along the y-axis. The `angle` is limited to the interval :math:`[0;\pi)`. An `angle` of 0 results in the same behaviour as that of uniform_line_sample_surface. An increase in the `angle` rotates the overall direction counterclockwise, i.e., at :math:`\frac{\pi}{2}`, the uniform_line_sample_surface sampling pattern is rotated 90 degrees counterclockwise. If the `follow_edge` flag is True, then the pattern follows the edges of the rectangular area when moving from one line to the next. If the flag is False, then the pattern follows a line perpendicular to the uniform lines when moving from one line to the next. In the latter case, some of the uniform lines are shortened to allow the described behaviour. Examples -------- For example, >>> import numpy as np >>> from magni.imaging.measurements import \ ... uniform_rotated_line_sample_surface >>> l = 1e-6 >>> w = 1e-6 >>> speed = 7e-7 >>> sample_rate = 1.0 >>> time = 12.0 >>> np.set_printoptions(suppress=True) >>> uniform_rotated_line_sample_surface(l, w, speed, sample_rate, time) array([[ 0. , 0. ], [ 0.00000067, 0. ], [ 0.00000083, 0.00000017], [ 0.00000017, 0.00000017], [ 0.00000033, 0.00000033], [ 0.000001 , 0.00000033], [ 0.0000005 , 0.0000005 ], [ 0. , 0.00000067], [ 0.00000067, 0.00000067], [ 0.00000083, 0.00000083], [ 0.00000017, 0.00000083], [ 0.00000033, 0.000001 ], [ 0.000001 , 0.000001 ]]) """ @_decorate_validation def validate_input(): _numeric('l', 'floating', range_='[{};inf)'.format(_min_l)) _numeric('w', 'floating', range_='[{};inf)'.format(_min_w)) _numeric('speed', 'floating', range_='[{};inf)'.format(_min_speed)) _numeric('sample_rate', 'floating', range_='[{};inf)'.format(_min_sample_rate)) _numeric('time', 'floating', range_='[{};inf)'.format(_min_time)) _numeric('angle', 'floating', range_='[0;{})'.format(np.pi)) _numeric('follow_edge', 'boolean') validate_input() if angle >= np.pi / 2: l, w = w, l angle = angle - np.pi / 2 rotate = True else: rotate = False cos = np.cos(angle) sin = np.sin(angle) if angle > 0: coords_corner = np.float_([[cos * w - sin * l, cos * l + sin * w]]) dir_l = -sin * coords_corner[0, 1] / cos dir_w = cos * coords_corner[0, 1] / sin def transform(coords): coords[:, 1] = -coords[:, 1] coords = coords.T coords = np.float_([[cos, -sin], [sin, cos]]).dot(coords) coords = coords.T coords[:, 1] = -coords[:, 1] return coords n = 3 coords_prev = np.zeros((3, 2)) while True: ratios = np.linspace(0, 1, n).reshape((n, 1)) if angle > 0: Y = ratios * coords_corner[0, 1] X_lower = np.maximum(dir_l * ratios, coords_corner[0, 0] - dir_w * (1 - ratios)) X_upper = np.minimum(dir_w * ratios, coords_corner[0, 0] - dir_l * (1 - ratios)) else: Y = ratios * l X_lower = 0 + ratios * 0 X_upper = w + ratios * 0 coords_lower = np.column_stack((X_lower, Y)) coords_upper = np.column_stack((X_upper, Y)) if follow_edge: coords_lower = transform(coords_lower) coords_upper = transform(coords_upper) coords = np.zeros((3 * n, 2)) # alternate between points coords[1::6] = coords_lower[0::2] coords[2::6] = coords_upper[0::2] coords[4::6] = coords_upper[1::2] coords[5::6] = coords_lower[1::2] # fill the blanks coords[3:-1:6, 0] = coords[4:-1:6, 0] coords[3:-1:6, 1] = coords[2:-1:6, 1] coords[6:-1:6, 0] = coords[5:-1:6, 0] coords[6:-1:6, 1] = coords[7:-1:6, 1] else: coords = np.zeros((2 * n, 2)) # alternate between points coords[0::4] = coords_lower[0::2] coords[1::4] = coords_upper[0::2] coords[2::4] = coords_upper[1::2] coords[3::4] = coords_lower[1::2] # shorten coords[1:-1:4, 0] = coords[2:-1:4, 0] = np.minimum(coords[1:-1:4, 0], coords[2:-1:4, 0]) coords[3:-1:4, 0] = coords[4:-1:4, 0] = np.maximum(coords[3:-1:4, 0], coords[4:-1:4, 0]) coords = transform(coords) length = coords[1:] - coords[:-1] length = np.sum(np.sqrt(length[:, 0]**2 + length[:, 1]**2)) if length > speed * time: n = n - 1 coords = coords_prev break else: n = n + 1 coords_prev = coords if rotate: l, w = w, l coords[:, 0], coords[:, 1] = coords[:, 1], l - coords[:, 0] X = coords[:, 0] X[X < 0] = 0 X[X > w] = w Y = coords[:, 1] Y[Y < 0] = 0 Y[Y > l] = l return _util.sample_lines(coords, speed, sample_rate, time)
def zigzag_sample_surface(l, w, speed, sample_rate, time, angle=np.pi / 20): r""" Sample a surface area using a zigzag pattern. The coordinates (in units of meters) resulting from sampling an area of size `l` times `w` using a zigzag pattern are determined. The scanned path is determined from the probe `speed` and the scan `time`. Parameters ---------- l : float The length of the area to scan in units of meters. w : float The width of the area to scan in units of meters. speed : float The probe speed in units of meters/second. sample_rate : float The sample rate in units of Hertz. time : float The scan time in units of seconds. angle : float The angle measured in radians by which the lines deviate from being horizontal (the default is pi / 20). Returns ------- coords : ndarray The coordinates of the samples arranged into a 2D array, such that each row is a coordinate pair (x, y). Notes ----- The orientation of the coordinate system is such that the width `w` is measured along the x-axis whereas the length `l` is measured along the y-axis. The `angle` is measured clockwise relative to horizontal and is limited to the interval :math:`\left(0;\arctan\left(\frac{h}{w}\right)\right)`. Examples -------- For example, >>> import numpy as np >>> from magni.imaging.measurements import zigzag_sample_surface >>> l = 1e-6 >>> w = 1e-6 >>> speed = 7e-7 >>> sample_rate = 1.0 >>> time = 12.0 >>> np.set_printoptions(suppress=True) >>> zigzag_sample_surface(l, w, speed, sample_rate, time) array([[ 0. , 0. ], [ 0.00000069, 0.00000011], [ 0.00000062, 0.00000022], [ 0.00000007, 0.00000033], [ 0.00000077, 0.00000044], [ 0.00000054, 0.00000055], [ 0.00000015, 0.00000066], [ 0.00000084, 0.00000077], [ 0.00000047, 0.00000088], [ 0.00000022, 0.00000099], [ 0.00000091, 0.0000009 ], [ 0.00000039, 0.0000008 ], [ 0.0000003 , 0.00000069]]) """ @_decorate_validation def validate_input(): _numeric('l', 'floating', range_='[{};inf)'.format(_min_l)) _numeric('w', 'floating', range_='[{};inf)'.format(_min_w)) _numeric('speed', 'floating', range_='[{};inf)'.format(_min_speed)) _numeric('sample_rate', 'floating', range_='[{};inf)'.format(_min_sample_rate)) _numeric('time', 'floating', range_='[{};inf)'.format(_min_time)) _numeric('angle', 'floating', range_='(0;{})'.format(np.arctan(l / w))) validate_input() length = w / np.cos(angle) height = length * np.sin(angle) number = speed * time / length coords = np.zeros((int(np.ceil(number)) + 1, 2)) coords[1::2, 0] = w coords[:, 1] = np.arange(np.ceil(number) + 1) * height coords[-1] = (coords[-2] + np.remainder(number, 1) * (coords[-1] - coords[-2])) coords = coords.repeat(2, axis=0) for i in range(coords.shape[0]): if coords[i, 1] < 0: coords[i] = (coords[i - 1, 0] + (coords[i - 1, 1] - 0) / height * (coords[i, 0] - coords[i - 1, 0]), 0) coords[i + 1:, 1] = -coords[i + 1:, 1] elif coords[i, 1] > l: coords[i] = (coords[i - 1, 0] + (l - coords[i - 1, 1]) / height * (coords[i, 0] - coords[i - 1, 0]), l) coords[i + 1:, 1] = 2 * l - coords[i + 1:, 1] return _util.sample_lines(coords, speed, sample_rate, time)
def random_line_sample_surface(l, w, speed, sample_rate, time, discrete=None, seed=None): """ Sample a surface area using a set of random straight lines. The coordinates (in units of meters) resulting from sampling an image of size `l` times `w` using a pattern based on a set of random straight lines are determined. The scanned path is determined from the probe `speed` and the scan `time`. If `discrete` is set, it specifies the finite number of equally spaced lines from which the scan lines are be chosen at random. For reproducible results, the `seed` may be used to specify a fixed seed of the random number generator. Parameters ---------- l : float The length of the area to scan in units of meters. w : float The width of the area to scan in units of meters. speed : float The probe speed in units of meters/second. sample_rate : float The sample rate in units of Hertz. time : float The scan time in units of seconds. discrete : int or None, optional The number of equally spaced lines from which the scan lines are chosen (the default is None, which implies that no discritisation is used). seed : int or None, optional The seed used for the random number generator (the defaul is None, which implies that the random number generator is not seeded). Returns ------- coords : ndarray The coordinates of the samples arranged into a 2D array, such that each row is a coordinate pair (x, y). Notes ----- The orientation of the coordinate system is such that the width `w` is measured along the x-axis whereas the length `l` is measured along the y-axis. Each of the scanned lines span the entire width of the image with the exception of the last line that may only be partially scanned if the `speed` and `time` implies this. The top and bottom lines of the image are always included in the scan and are not included in the `discrete` number of lines. Examples -------- For example, >>> import numpy as np >>> from magni.imaging.measurements import random_line_sample_surface >>> l = 2e-6 >>> w = 2e-6 >>> speed = 7e-7 >>> sample_rate = 1.0 >>> time = 12.0 >>> seed = 6021 >>> np.set_printoptions(suppress=True) >>> random_line_sample_surface(l, w, speed, sample_rate, time, seed=seed) array([[ 0. , 0. ], [ 0.00000067, 0. ], [ 0.00000133, 0. ], [ 0.000002 , 0. ], [ 0.000002 , 0.00000067], [ 0.000002 , 0.00000133], [ 0.00000158, 0.00000158], [ 0.00000091, 0.00000158], [ 0.00000024, 0.00000158], [ 0. , 0.000002 ], [ 0.00000067, 0.000002 ], [ 0.00000133, 0.000002 ], [ 0.000002 , 0.000002 ]]) """ @_decorate_validation def validate_input(): _numeric('l', 'floating', range_='[{};inf)'.format(_min_l)) _numeric('w', 'floating', range_='[{};inf)'.format(_min_w)) _numeric('speed', 'floating', range_='[{};inf)'.format(_min_speed)) _numeric('sample_rate', 'floating', range_='[{};inf)'.format(_min_sample_rate)) _numeric('time', 'floating', range_='[{};inf)'.format(_min_time)) _numeric('discrete', 'integer', range_='[2;inf)', ignore_none=True) _numeric('seed', 'integer', range_='[0;inf)', ignore_none=True) if (speed * time - 2 * w - l) / w <= -1: # Estimated number of lines in addition to top and bottom lines # must exceed -1 to avoid drawing a negative number of lines at # random. msg = ('The value of >>(speed * time - 2 * w - l) / w<<, {!r}, ' 'must be > -1.') raise ValueError(msg.format((speed * time - 2 * w - l) / w)) validate_input() if seed is not None: np.random.seed(seed) num_lines = int(np.floor((speed * time - l) / w)) if discrete is None: lines = np.sort(np.random.rand(num_lines - 2) * l) else: possible_lines = l / (discrete + 1) * np.arange(1, discrete + 1) try: lines = np.sort( np.random.choice(possible_lines, size=num_lines - 2, replace=False)) except ValueError: raise ValueError('The number of Discrete lines must be large ' + 'enough to contain the entire scan path. With ' + 'the current settings, a minimun of ' '{!r} lines are required.'.format(num_lines - 2)) coords = np.zeros((2 * num_lines, 2)) coords[1::4, 0] = coords[2::4, 0] = w coords[2:-2:2, 1] = coords[3:-2:2, 1] = lines coords[-2:, 1] = l return _util.sample_lines(coords, speed, sample_rate, time)
def uniform_line_sample_surface(l, w, speed, sample_rate, time): """ Sample aa surface area using a set of uniformly distributed straight lines. The coordinates (in units of meters) resulting from sampling an area of size `l` times `w` using a pattern based on a set of uniformly distributed straight lines are determined. The scanned path is determined from the probe `speed` and the scan `time`. Parameters ---------- l : float The length of the area to scan in units of meters. w : float The width of the area to scan in units of meters. speed : float The probe speed in units of meters/second. sample_rate : float The sample rate in units of Hertz. time : float The scan time in units of seconds. Returns ------- coords : ndarray The coordinates of the samples arranged into a 2D array, such that each row is a coordinate pair (x, y). Notes ----- The orientation of the coordinate system is such that the width `w` is measured along the x-axis whereas the height `l` is measured along the y-axis. Each of the scanned lines span the entire width of the image with the exception of the last line that may only be partially scanned if the `scan_length` implies this. The top and bottom lines of the image are always included in the scan. Examples -------- For example, >>> import numpy as np >>> from magni.imaging.measurements import uniform_line_sample_surface >>> l = 2e-6 >>> w = 2e-6 >>> speed = 7e-7 >>> sample_rate = 1.0 >>> time = 12.0 >>> np.set_printoptions(suppress=True) >>> uniform_line_sample_surface(l, w, speed, sample_rate, time) array([[ 0. , 0. ], [ 0.00000067, 0. ], [ 0.00000133, 0. ], [ 0.000002 , 0. ], [ 0.000002 , 0.00000067], [ 0.00000167, 0.000001 ], [ 0.000001 , 0.000001 ], [ 0.00000033, 0.000001 ], [ 0. , 0.00000133], [ 0. , 0.000002 ], [ 0.00000067, 0.000002 ], [ 0.00000133, 0.000002 ], [ 0.000002 , 0.000002 ]]) """ @_decorate_validation def validate_input(): _numeric('l', 'floating', range_='[{};inf)'.format(_min_l)) _numeric('w', 'floating', range_='[{};inf)'.format(_min_w)) _numeric('speed', 'floating', range_='[{};inf)'.format(_min_speed)) _numeric('sample_rate', 'floating', range_='[{};inf)'.format(_min_sample_rate)) _numeric('time', 'floating', range_='[{};inf)'.format(_min_time)) validate_input() num_lines = int(np.floor((speed * time - l) / w)) # We should always at least partially scan top and bottom lines. if num_lines < 2: num_lines = 2 coords = np.zeros((2 * num_lines, 2)) coords[1::4, 0] = coords[2::4, 0] = w coords[0::2, 1] = coords[1::2, 1] = np.linspace(0, l, num_lines) return _util.sample_lines(coords, speed, sample_rate, time)