Skip to content

openstb.simulator.target.points

Classes:

Name Description
PointLine

A regularly spaced line of points.

RandomPointRect

A set of points uniformly distributed within a rectangle.

RandomPointTriangle

A set of point targets uniformly distributed within a triangle.

SinglePoint

A single point target.

PointLine

PointLine(num_points, reflectivity, start_position, end_position=None, spacing=None)

Bases: PointTargets

A regularly spaced line of points.

Parameters:

Name Type Description Default
num_points int

The number of points in the line.

required
reflectivity float

The reflectivity of the targets (the fraction of incident energy that will scatter back to the receiver).

required
start_position ArrayLike

The position of the first point in the line.

required
end_position ArrayLike | None

The position of the last point in the line. Cannot be given if spacing is.

None
spacing ArrayLike | None

The spacing between neighbouring points in the line. Cannot be given if end_position is, and must be given if end_position is not.

None
Source code in openstb/simulator/target/points.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def __init__(
    self,
    num_points: int,
    reflectivity: float,
    start_position: ArrayLike,
    end_position: ArrayLike | None = None,
    spacing: ArrayLike | None = None,
):
    """
    Parameters
    ----------
    num_points
        The number of points in the line.
    reflectivity
        The reflectivity of the targets (the fraction of incident energy that will
        scatter back to the receiver).
    start_position
        The position of the first point in the line.
    end_position
        The position of the last point in the line. Cannot be given if `spacing` is.
    spacing
        The spacing between neighbouring points in the line. Cannot be given if
        `end_position` is, and must be given if `end_position` is not.

    """
    self.start_position = np.atleast_1d(start_position)
    if self.start_position.shape != (3,):
        raise ValueError(_("start_position must have 3 elements"))

    self.reflectivity = reflectivity
    self.num_points = num_points

    if end_position is None and spacing is None:
        raise ValueError(_("exactly one of end_position or spacing must be given"))
    if end_position is not None and spacing is not None:
        raise ValueError(_("exactly one of end_position or spacing must be given"))

    if spacing is not None:
        self.spacing = np.atleast_1d(spacing)
        if self.spacing.shape != (3,):
            raise ValueError(_("spacing must have 3 elements"))
    if end_position is not None:
        end_position = np.atleast_1d(end_position)
        if end_position.shape != (3,):
            raise ValueError(_("end_position must have 3 elements"))
        self.spacing = (end_position - start_position) / (self.num_points - 1)

RandomPointRect

RandomPointRect(seed, Dx, Dy, centre, normal, point_density, reflectivity)

Bases: PointTargets

A set of points uniformly distributed within a rectangle.

The points are placed uniformly at a given density within a rectangle of a set size in the x-y plane, and are then translated and rotated to the desired position and orientation.

Parameters:

Name Type Description Default
seed positive int

Seed for the random number generator.

required
Dx float

Size (in metres) of the rectangle in the x-y plane prior to transformation.

required
Dy float

Size (in metres) of the rectangle in the x-y plane prior to transformation.

required
centre array - like

A 3-element vector giving the centre of the final rectangle in global coordinates.

required
normal array - like

A 3-element vector giving the normal of the final rectangle. Remember that the z axis points down, so an upwards-facing rectangle would have a normal with a negative z value. The vector does not need to have unit length, but cannot have zero length.

required
point_density float

The density of the points in points per square metre.

required
reflectivity (float, {omnidirectional, hemispherical})

The reflectivity of the point targets. This is a amplitude scaling factor applied to the incident pulse to get the scattered pulse. The special values "omnidirectional" and "hemispherical" model omnidirectional (uniform scattering over the sphere) and hemispherical (uniform scattering over the hemisphere facing the sonar) scattering and correspond to 1/4pi and 1/2pi respectively.

required
Source code in openstb/simulator/target/points.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def __init__(
    self,
    seed: int,
    Dx: float,
    Dy: float,
    centre: ArrayLike,
    normal: ArrayLike,
    point_density: float,
    reflectivity: str | float,
):
    """
    Parameters
    ----------
    seed : positive int
        Seed for the random number generator.
    Dx, Dy : float
        Size (in metres) of the rectangle in the x-y plane prior to transformation.
    centre : array-like
        A 3-element vector giving the centre of the final rectangle in global
        coordinates.
    normal : array-like
        A 3-element vector giving the normal of the final rectangle. Remember that
        the z axis points down, so an upwards-facing rectangle would have a normal
        with a negative z value. The vector does not need to have unit length, but
        cannot have zero length.
    point_density : float
        The density of the points in points per square metre.
    reflectivity : float, {"omnidirectional", "hemispherical"}
        The reflectivity of the point targets. This is a amplitude scaling factor
        applied to the incident pulse to get the scattered pulse. The special values
        "omnidirectional" and "hemispherical" model omnidirectional (uniform
        scattering over the sphere) and hemispherical (uniform scattering over the
        hemisphere facing the sonar) scattering and correspond to 1/4pi and 1/2pi
        respectively.

    """
    self.seed = seed
    self.Dx = Dx
    self.Dy = Dy
    self.point_density = point_density

    # Check the centre is valid.
    self.centre = np.array(centre)
    if self.centre.shape != (3,):
        raise ValueError(
            _("centre position of rectangle must have exactly 3 values")
        )

    # Check the size is valid.
    if self.Dx < 0 or np.isclose(Dx, 0):
        raise ValueError(_("x size of rectangle must be positive"))
    if self.Dy < 0 or np.isclose(Dy, 0):
        raise ValueError(_("y size of rectangle must be positive"))

    # Figure out the number of points needed to meet the density.
    area = self.Dx * self.Dy
    self._len = int(np.round(area * self.point_density))
    if self._len < 0:
        raise ValueError(_("no points placed in rectangle"))

    # Check and scale the given normal.
    normal = np.array(normal, dtype=float)
    if normal.shape != (3,):
        raise ValueError(_("normal of rectangle must have exactly 3 values"))
    nlen = np.linalg.norm(normal)
    if np.isclose(nlen, 0):
        raise ValueError(_("length of rectangle normal cannot be zero"))
    normal /= nlen

    # Convert to a quaternion we can use for rotating the generated points.
    dp = np.dot(normal, [0, 0, -1])
    if np.isclose(dp, 1):
        self._ori = quaternionic.array([1, 0, 0, 0])
    elif np.isclose(dp, -1):
        self._ori = quaternionic.array([0, 1, 0, 0])
    else:
        angle = np.arccos(dp)
        axis = np.cross(normal, [0, 0, -1])
        axis /= np.linalg.norm(axis)
        c = np.cos(angle / 2)
        s = np.sin(angle / 2)
        self._ori = quaternionic.array([c, s * axis[0], s * axis[1], s * axis[2]])

    # Handle the specific cases for the reflectivity.
    if isinstance(reflectivity, str):
        rlower = reflectivity.lower()
        if rlower == "omnidirectional":
            self._reflectivityval = 1 / (4 * np.pi)
        elif rlower == "hemispherical":
            self._reflectivityval = 1 / (2 * np.pi)
        else:
            raise ValueError(
                _("unexpected value '{value}' for reflectivity").format(
                    value=reflectivity
                )
            )

    # Assume a specified value.
    else:
        self._reflectivityval = reflectivity

    # Initialise the RNG.
    self._rng = ChunkedRNG(self.seed, "uniform", 2)

RandomPointTriangle

RandomPointTriangle(seed, p1, p2, p3, point_density, reflectivity)

Bases: PointTargets

A set of point targets uniformly distributed within a triangle.

Parameters:

Name Type Description Default
seed int

The seed for the random number generator which places the point targets.

required
p1 ArrayLike

The position of the first corner of the triangle.

required
p2 ArrayLike

The position of the second corner of the triangle.

required
p3 ArrayLike

The position of the third corner of the triangle.

required
point_density float

The density of the point targets in targets per square metre.

required
reflectivity float | Literal['omnidirectional', 'hemispherical']

The reflectivity of the point targets. This is a amplitude scaling factor applied to the incident pulse to get the scattered pulse. The special values "omnidirectional" and "hemispherical" model omnidirectional (uniform scattering over the sphere) and hemispherical (uniform scattering over the hemisphere facing the sonar) scattering and correspond to 1/4pi and 1/2pi respectively.

required
Source code in openstb/simulator/target/points.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
def __init__(
    self,
    seed: int,
    p1: ArrayLike,
    p2: ArrayLike,
    p3: ArrayLike,
    point_density: float,
    reflectivity: float | Literal["omnidirectional", "hemispherical"],
):
    """
    Parameters
    ----------
    seed
        The seed for the random number generator which places the point targets.
    p1
        The position of the first corner of the triangle.
    p2
        The position of the second corner of the triangle.
    p3
        The position of the third corner of the triangle.
    point_density
        The density of the point targets in targets per square metre.
    reflectivity
        The reflectivity of the point targets. This is a amplitude scaling factor
        applied to the incident pulse to get the scattered pulse. The special values
        "omnidirectional" and "hemispherical" model omnidirectional (uniform
        scattering over the sphere) and hemispherical (uniform scattering over the
        hemisphere facing the sonar) scattering and correspond to 1/4pi and 1/2pi
        respectively.

    """
    self.seed = seed
    if point_density < 0:
        raise ValueError(_("point density cannot be negative"))
    self.point_density = point_density

    # Check the points are the right size.
    self.p1 = np.atleast_1d(p1)
    if self.p1.shape != (3,):
        raise ValueError(_("p1 must have exactly three values"))
    self.p2 = np.atleast_1d(p2)
    if self.p2.shape != (3,):
        raise ValueError(_("p2 must have exactly three values"))
    self.p3 = np.atleast_1d(p3)
    if self.p3.shape != (3,):
        raise ValueError(_("p3 must have exactly three values"))

    # Generate the vectors and check they are non-zero.
    self.v2 = self.p2 - self.p1
    l2 = np.linalg.norm(self.v2)
    if np.isclose(l2, 0):
        raise ValueError(_("p1 and p2 cannot be coincident"))
    self.v3 = self.p3 - self.p1
    l3 = np.linalg.norm(self.v3)
    if np.isclose(l3, 0):
        raise ValueError(_("p1 and p3 cannot be coincident"))

    # Check the vectors are not colinear.
    angle = np.arccos(np.dot(self.v2, self.v3) / (l2 * l3))
    if np.isclose(angle, 0) or np.isclose(angle, np.pi):
        raise ValueError(_("points cannot be colinear"))

    # Number of points needed to meet the density.
    area = 0.5 * l2 * l3 * np.sin(angle)
    self._len = int(np.round(area * self.point_density))
    if self._len < 1:
        raise ValueError(_("no point targets in triangle"))

    # Handle the special cases of the reflectivity.
    if reflectivity == "omnidirectional":
        self._reflectivity_val = 1 / (4 * np.pi)
    elif reflectivity == "hemispherical":
        self._reflectivity_val = 1 / (2 * np.pi)
    else:
        self._reflectivity_val = float(reflectivity)

    # Initialise the RNG.
    self._rng = ChunkedRNG(self.seed, "uniform", 2)

SinglePoint

SinglePoint(position, reflectivity)

Bases: PointTargets

A single point target.

Parameters:

Name Type Description Default
position array - like

The position of the point target in global coordinates.

required
reflectivity float

The reflectivity of the target (the fraction of incident energy that will scatter back to the receiver).

required
Source code in openstb/simulator/target/points.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
def __init__(self, position: ArrayLike, reflectivity: float):
    """
    Parameters
    ----------
    position : array-like
        The position of the point target in global coordinates.
    reflectivity : float
        The reflectivity of the target (the fraction of incident energy that will
        scatter back to the receiver).

    """
    self._position = np.array(position).reshape(1, 3)
    self._reflectivity = np.array(reflectivity).reshape(
        1,
    )