"""
..
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
----------------
random_line_sample_image(h, w, scan_length, num_points, discrete=None,
seed=None)
Function for random line sampling an image.
random_line_sample_surface(l, w, speed, sample_rate, time, discrete=None,
seed=None)
Function for random line 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__ = ['random_line_sample_image', 'random_line_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 random_line_sample_image(h, w, scan_length, num_points, discrete=None,
seed=None):
"""
Sample an image using a set of random straight lines.
The coordinates (in units of pixels) resulting from sampling an image of
size `h` times `w` using a pattern based on a set of random straight lines
are determined. The `scan_length` determines the length of the path scanned
whereas `num_points` indicates the number of samples taken on that path. 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
----------
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.
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 height `h` 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 random_line_sample_image
>>> h = 10
>>> w = 10
>>> scan_length = 50.0
>>> num_points = 12
>>> seed = 6021
>>> np.set_printoptions(suppress=True)
>>> random_line_sample_image(h, w, scan_length, num_points, seed=seed)
array([[ 0.5 , 0.5 ],
[ 4.59090909, 0.5 ],
[ 8.68181818, 0.5 ],
[ 7.01473938, 1.28746666],
[ 2.92383029, 1.28746666],
[ 0.5 , 2.95454545],
[ 0.5 , 7.04545455],
[ 4.03665944, 7.59970419],
[ 8.12756853, 7.59970419],
[ 8.68181818, 9.5 ],
[ 4.59090909, 9.5 ],
[ 0.5 , 9.5 ]])
"""
@_decorate_validation
def validate_input():
_numeric('h', 'integer', range_='[2;inf)')
_numeric('w', 'integer', range_='[2;inf)')
_numeric('scan_length', 'floating',
range_='[{};inf)'.format(_min_scan_length))
_numeric('num_points', 'integer',
range_='[{};inf)'.format(_min_num_points))
_numeric('discrete', 'integer', range_='[2;inf)', ignore_none=True)
_numeric('seed', 'integer', range_='[0;inf)', ignore_none=True)
validate_input()
coords = random_line_sample_surface(float(h - 1), float(w - 1),
scan_length, float(num_points - 1),
1.0, discrete=discrete, seed=seed)
coords = coords + 0.5
return coords
[docs]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)