Source code for magni.imaging.measurements._spiral

"""
..
    Copyright (c) 2014-2017, Magni developers.
    All rights reserved.
    See LICENSE.rst for further information.

Module providing public functions for the magni.imaging.measurements
subpackage.

Routine listings
----------------
spiral_sample_image(h, w, scan_length, num_points, rect_area=False)
    Function for spiral sampling an image.
spiral_sample_surface(l, w, speed, sample_rate, time, rect_area=False)
    Function for spiral sampling a surface.

"""

from __future__ import division

import numpy as np

from magni.imaging.measurements import _util
from magni.utils.validation import decorate_validation as _decorate_validation
from magni.utils.validation import validate_numeric as _numeric


__all__ = ['spiral_sample_image', 'spiral_sample_surface']

_min_l = _util.min_l
_min_w = _util.min_w
_min_speed = _util.min_speed
_min_sample_rate = _util.min_sample_rate
_min_time = _util.min_time
_min_scan_length = _util.min_scan_length
_min_num_points = _util.min_num_points


[docs]def spiral_sample_image(h, w, scan_length, num_points, rect_area=False): """ Sample an image using an archimedean spiral pattern. The coordinates (in units of pixels) resulting from sampling an image of size `h` times `w` using an archimedean spiral pattern are determined. The `scan_length` determines the length of the path scanned whereas `num_points` indicates the number of samples taken on that path. Parameters ---------- h : int The height of the area to scan in units of pixels. w : int The width of the area to scan in units of pixels. scan_length : float The length of the path to scan in units of pixels. num_points : int The number of samples to take on the scanned path. rect_area : bool A flag indicating whether or not the full rectangular area is sampled (the default value is False which implies that the "corners" of the rectangular area are not sampled). 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 `h` is measured along the y-axis. The width must equal the height for an archimedian spiral to make sense. If the `rect_area` flag is True, then it is assumed that the sampling continues outside of the rectangular area specified by `h` and `w` such that the "corners" of the rectangular area are also sampled. The sample points outside of the rectangular area are discarded and, hence, not returned. Examples -------- For example, >>> import numpy as np >>> from magni.imaging.measurements import spiral_sample_image >>> h = 10 >>> w = 10 >>> scan_length = 50.0 >>> num_points = 12 >>> np.set_printoptions(suppress=True) >>> spiral_sample_image(h, w, scan_length, num_points) array([[ 6.28776846, 5.17074073], [ 3.13304898, 5.24133767], [ 6.07293751, 2.93873701], [ 6.99638041, 6.80851189], [ 2.89868434, 7.16724999], [ 2.35773914, 3.00320067], [ 6.41495385, 1.71018152], [ 8.82168896, 5.27557847], [ 6.34932919, 8.83624957], [ 2.04885699, 8.11199373], [ 0.6196052 , 3.96939755]]) """ @_decorate_validation def validate_input(): _numeric('h', 'integer', range_='[2;inf)') _numeric('w', 'integer', range_='[2;inf)') if h != w: msg = ('The value of >>h<<, {!r}, must equal the value of >>w<<, ' '{!r}, for an archimedian spiral to make sense.') raise ValueError(msg.format(h, w)) _numeric('scan_length', 'floating', range_='[{};inf)'.format(_min_scan_length)) _numeric('num_points', 'integer', range_='[{};inf)'.format(_min_num_points)) _numeric('rect_area', 'boolean') validate_input() coords = spiral_sample_surface(float(h - 1), float(w - 1), scan_length, float(num_points), 1.0, rect_area) coords = coords + 0.5 return coords
[docs]def spiral_sample_surface(l, w, speed, sample_rate, time, rect_area=False): """ Sample a surface area using an archimedean spiral pattern. The coordinates (in units of meters) resulting from sampling an area of size `l` times `w` using an archimedean spiral 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. rect_area : bool A flag indicating whether or not the full rectangular area is sampled (the default value is False which implies that the "corners" of the rectangular area are not sampled). 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 width must equal the length for an archimedian sprial to make sense. If the `rect_area` flag is True, then it is assumed that the sampling continues outside of the rectangular area specified by `l` and `w` such that the "corners" of the rectangular area are also sampled. The sample points outside of the rectangular area are discarded and, hence, not returned. Examples -------- For example, >>> import numpy as np >>> from magni.imaging.measurements import spiral_sample_surface >>> l = 1e-6 >>> w = 1e-6 >>> speed = 7e-7 >>> sample_rate = 1.0 >>> time = 12.0 >>> np.set_printoptions(suppress=True) >>> spiral_sample_surface(l, w, speed, sample_rate, time) array([[ 0.00000036, 0.00000046], [ 0.00000052, 0.00000071], [ 0.00000052, 0.00000024], [ 0.00000059, 0.00000079], [ 0.00000021, 0.00000033], [ 0.00000084, 0.00000036], [ 0.00000049, 0.0000009 ], [ 0.0000001 , 0.00000036], [ 0.00000072, 0.00000011], [ 0.00000089, 0.00000077], [ 0.00000021, 0.00000091]]) """ @_decorate_validation def validate_input(): _numeric('l', 'floating', range_='[{};inf)'.format(_min_l)) _numeric('w', 'floating', range_='[{};inf)'.format(_min_w)) if l != w: msg = ('The value of >>h<<, {!r}, must equal the value of >>w<<, ' '{!r}, for an archimedian spiral to make sense.') raise ValueError(msg.format(l, 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('rect_area', 'boolean') validate_input() sample_period = 1 / sample_rate r_end = np.min([l, w]) / 2 if rect_area: # Sample the "corners" of a rectangular area r_end *= np.sqrt(2) pitch = np.pi * r_end ** 2 / (speed * time) # Starting at t=0 yields a divide-by-zero problem. Therefore we start the # spiral at t=sample-period sample_times = np.linspace(sample_period, time, sample_rate * time - 1) r = np.sqrt(pitch * speed / np.pi * sample_times) theta = np.sqrt(4 * np.pi * speed * sample_times / pitch) coords = np.zeros((len(sample_times), 2)) coords[:, 0] = r * np.cos(theta) coords[:, 1] = r * np.sin(theta) # FIXME: This simple stretch yields a non-constant linear speed, which # clashes with the idea of the current interface to all sample methods. """ if w > l: coords[:, 0] = coords[:, 0] * w / l else: coords[:, 1] = coords[:, 1] * l / w """ if rect_area: # We assume the tip follows the spiral outside of the rectangular area # These coordinates should not be included in the sampled area within_rect_area = np.where( np.all(np.abs(coords) <= (w / 2, l / 2), axis=1)) coords = coords[within_rect_area] coords = coords + np.array([w / 2, l / 2]) return coords