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)
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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)