FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
Echosounder

Downward and upward looking split-beam echosounder: an altimeter on its ports and a three-frequency echogram in a file.

+ Collaboration diagram for Echosounder:
Review issues:

0039 — Echosounder Altitude is the beam projection, not the vertical clearance, over sloping ground

0040 — The echosounder upward beam bins one range bin above the sea surface

0041 — SimObject render teardown runs after the Ogre root is destroyed

Author
Karl-Johan Reite

The instrument has two halves, and they are separated deliberately.

The altimeter half is on the output ports. One ray is cast along the downward beam with marenv::EnvironmentFacade::ComputeRayIntersection, the slant range is perturbed with a bias and two noise terms, and the result is published as SeafloorDistance, the inferred bottom depth SeafloorDepth, the height above the bottom Altitude, a Valid flag and the PingTime the reading belongs to. That is what a dive controller consumes, and it is a handful of doubles per ping.

The echogram half is a file, because it is not: one ping is six rows of up to a few hundred Sv values each, at two pings per second. Putting that on a port would mean a port of a size fixed at construction, padded with NaN, interpolated by the observer and written into every regression CSV in the tree. See SensorEchosounder_file below.

<tt>Altitude</tt> is the <em>measured</em> twin of <tt>Auv.Altitude</tt>

Auv already publishes a true Altitude, computed from GetSeaDepth at the vehicle's own position. This class publishes a measured Altitude with the same name, the same sign and the same unit, so putting the sensor in the control loop is a one line rewire in XML and nothing downstream changes:

<Connection Dive.Altitude = "Auv.Altitude"/> <!-- truth -->
<Connection Dive.Altitude = "Echo.Altitude"/> <!-- sensor in the loop -->

The two differ by the range bias, the range noise, the ping interval and, over sloping ground, by the beam geometry: this class reports the vertical projection of the slant range, \(R\cos\theta\), which is the true vertical clearance only where the bottom is locally flat. On a slope it reads the clearance to the point the beam actually hits, which is what a real altimeter does too.

The beam geometry

The downward beam axis is the SensorFrame MountDirection, rotated into NED by the carrier attitude and normalised. It is not defaulted for you: SensorFrame defaults MountDirection to the body x axis, which for a nadir sounder is wrong by 90 degrees, so a sounder configuration must set MountDirection = "0, 0, 1". One warning is given at FinalSetup if the mounted beam does not point downwards in the body frame at all.

The upward beam is the exact negation of the downward one. A single physical transducer pair on the same mount is the usual arrangement, and it means a pitch or roll of the carrier tilts both beams together, as it must.

Frequencies

38, 120 and 200 kHz. 38 kHz is the de facto fish biomass and bottom detection standard and the reference frequency of nearly every published target strength to length relation; 120 kHz separates a mackerel layer from a gadoid one; 200 kHz is where a 2.8 mm Calanus first becomes visible at all.

Note
333 kHz would image Calanus better still, but its absorption is about 90 dB/km, which limits it to some 40 m of useful range. That will not reach the bottom from mid water, so it is not offered as a default.
Refraction is neglected. A temperature front bends a real beam, but these beams are short and steep, so straight line propagation is used and no ray tracer is built.

Noise, and what <tt>NoiseSeed = 0</tt> means

NoiseSeed = 0 is the perfect instrument, on both halves: no range bias, no range noise, no acoustic noise floor and no speckle. The echogram is then the pure forward model and can be compared against a closed form, which is exactly what the unit tests do. Any other seed enables all four. See SensorNoise.

Latency, dropouts and outliers belong downstream

As with the other AUV sensors there are deliberately no TimeDelay, LossProbability or OutlierProbability parameters. Chain Altitude into a fhsim_base Signal/Samplifier if that is wanted:

<Lib LibName = "base" SimObject = "Signal/Samplifier" Name = "EchoTelemetry"
SampleTime = "0.5" TimeDelay = "0.2" LossProbability = "0.02" Size = "1"/>
<Connection EchoTelemetry.In = "Echo.Altitude"/>
Note
fhsim_base is not in this package's Conan closure, see 0028 — Input files require the `base` SimObject library, which the package does not depend on, so the excerpt above is documentation rather than a fixture in this repository.

The echogram file

Written only when WriteFile is non-zero. The ofstream is opened and the header written in FinalSetup, one row per beam and frequency is appended in AcceptedStep, and the stream is flushed and closed in the destructor. Nothing is ever written in OdeFcn, which is const and is re-entered at rejected trial states; a file written there would contain the integrator's discarded attempts.

Six rows per ping, three frequencies by two beams. Each row carries its own NumBins and exactly that many Sv values, so a row is as long as the beam is, rather than padded to a fixed width with NaN. A 600 s mission at the defaults is about 7.5 MB; FlushEveryNPings, SvPrecision and WriteFile are the escape hatches.

FileName is kept relative on purpose, so that an FMU host controls where the file lands through its own path resolution rather than this class hard coding a directory.

The acoustic model, per bin

Both beams are binned at BinSize, bin \(k\) covering the slant range \([k\,\Delta, (k+1)\Delta)\) and evaluated at its centre. Every species field is queried once per bin, not once per bin per frequency: that single caching decision divides the field evaluations per ping by the number of frequencies. Concretely, a sounder 40 m down over a 140 m seabed bins 101 m down and 40 m up, so with three species it is 426 evaluations per ping instead of 1278, and at the full 150 m search range in both directions it is about 900 instead of 2700.

From the cached number concentrations \(n_i\), per frequency \(f\):

\[ TS_i(f) = 20\log_{10} L_i + TS^{ref}_i + s_i \log_2(f / f^{ref}_i), \qquad s_v = \sum_i n_i\,10^{TS_i(f)/10} \]

In the bottom bin of the downward beam a bottom echo \(10^{B(f)/10} / \Delta\) is added, which is the surface backscattering coefficient spread over the bin that contains it.

A range dependent noise floor is added to every bin:

\[ s_{noise} = 10^{(N(f) + 20\log_{10} r + 2\alpha(f) r/1000)/10} \]

This is the physically right imperfection rather than a cosmetic one: weak Calanus disappears into it at long range exactly as it does in reality, and that is the whole reason for carrying the sounder on an AUV instead of hanging it under a ship.

The bin is then multiplied by a Gamma draw of shape IndependentSamplesPerBin and unit mean, which is Rayleigh speckle averaged over that many independent samples, about 1.5 dB at the default of 8. The speckle is multiplicative in linear units, not additive in dB; an additive-dB model would be biased and would have the wrong tail.

Finally \(S_v = \max(10\log_{10} s_v, \texttt{SvFloorDb}) + \texttt{SvBiasDb}(f)\).

Absorption is applied to the noise floor and not to <tt>Sv</tt>

This reads as an omission and is not one. \(S_v\) is by definition a TVG compensated quantity: a real sounder has already multiplied the received echo by \(r^2 10^{2\alpha r/1000}\) before reporting \(S_v\), so applying absorption to the modelled \(S_v\) here would double count it. The noise floor, on the other hand, is referred to the received signal, so it grows with range through exactly that same TVG — which is why absorption appears there and only there. The statement is repeated in the file header, where the next reader of a CSV will be.

Species fields

SpeciesFieldNames defaults to the explicit list "Calanus, Mackerel, Cod" and the resolved list is written into the file header. Discovery by Quantity is available through AutoDiscoverSpecies, but it is opt in, because making the column semantics of a file format depend on the registration order of the environment is fragile.

A named field that is not registered is a fatal error. A field that is registered but is missing one of the mandatory acoustic properties MassKg, LengthM or TargetStrengthRefDb is excluded from the acoustic model with one warning at FinalSetup, not an error: a plankton field that only the SilCam cares about should not stop the sounder from running.

The optional properties are RefFreqkHz, the frequency the target strength was measured at, default 38, and TsSlopeDbPerOctave, the slope of the target strength with frequency, default 0 for a flat gadoid-like response.

Example configuration excerpt

<Lib
LibName = "fishery"
SimObject = "Auv/Echosounder"
Name = "Echo"
MountOffset = "0.5, 0, 0.1"
MountDirection = "0, 0, 1"
PingPeriod = "0.5"
MaxRangeDown = "150"
MaxRangeUp = "150"
BeamWidth = "7"
SpeciesFieldNames = "Calanus, Mackerel, Cod"
FrequencieskHz = "38, 120, 200"
AbsorptionDbPerKm = "10, 33, 52"
BottomBackscatterDb = "-25, -23, -22"
NoiseSvAt1mDb = "-125, -128, -130"
WriteFile = "1"
FileName = "AuvSurvey_echogram.csv"
NoiseSeed = "12345"
/>
<Connection Echo.Pos = "Auv.Pos" Echo.Quater = "Auv.Quater"/>
Note
The ping instants depend on where the accepted integrator steps fall, so a regression input must fix StepMax, and well below PingPeriod. SampleClock warns once if a ping is taken more than a quarter of a period late.

Input Ports

Name Width Description @addoption{Pos, 3, The carrier body origin in NED, m. Held by the SensorFrame. } @addoption{Quater, 4, The carrier body attitude, scalar first [q0 q1 q2 q3]. Held by the SensorFrame. }

Output Ports

Name Width Description @addoption{SeafloorDistance, 1, The measured slant range along the downward beam to the seafloor, m. Held between pings. When no bottom was found this is MaxRangeDown, which is a lower bound rather than a measurement, and Valid is zero. } @addoption{SeafloorDepth, 1, The inferred depth of the seafloor below the mean water level at the point the beam hit, m. } @addoption{Altitude, 1, The measured height of the sensor above the seafloor, m, that is the vertical projection of the slant range. The measured twin of the true Auv.Altitude. } @addoption{Valid, 1, One when the last ping found a bottom within MaxRangeDown, zero otherwise. A dive controller must check this. } @addoption{PingTime, 1, The simulation time of the ping the held reading came from, s. }

Configuration parameters

Name Width Description @addoption{MountOffset, 3, The transducer position in the body frame, m. Default 0 0 0. } @addoption{MountDirection, 3, The downward beam axis in the body frame; the upward beam is its negation. Need not be unit. Default 1 0 0 from the shared SensorFrame, which is wrong for a sounder: set it to 0 0 1. } @addoption{PingPeriod, 1, The time between pings, s. Default 0.5. } @addoption{MaxRangeDown, 1, The longest slant range searched along the downward beam, m. Default 150. Beyond it the bottom counts as not found. } @addoption{MaxRangeUp, 1, The longest slant range binned along the upward beam, m. Default 150. The upward beam stops at the sea surface whenever that is nearer. } @addoption{RayStepSize, 1, The search step of ComputeRayIntersection, m. Default 1. } @addoption{RayTolerance, 1, The vertical convergence tolerance of ComputeRayIntersection, m. Default 0.01. It bounds how well SeafloorDepth can agree with the bathymetry. } @addoption{RangeBias, 1, A constant offset added to every measured slant range, m. Default 0.02. } @addoption{RangeNoiseStd, 1, The standard deviation of the absolute range noise, m. Default 0.02. } @addoption{RangeNoiseRelative, 1, The standard deviation of the range noise proportional to the range. Default 0.0005, that is 0.05 per cent. } @addoption{SpeciesFieldNames, n, The comma separated names of the species scalar fields entering the acoustic model. Default Calanus, Mackerel, Cod. A name that is not registered is a fatal error. } @addoption{AutoDiscoverSpecies, 1, When non-zero, every registered field tagged SpeciesConcentration is used and SpeciesFieldNames is ignored. Default 0, because a file format whose columns depend on registration order is fragile. } @addoption{FrequencieskHz, n, The operating frequencies, kHz. Default 38, 120, 200. The four per-frequency arrays below must all have this same length. } @addoption{AbsorptionDbPerKm, n, The absorption coefficient at each frequency, dB/km. Default 10, 33, 52. Applied to the noise floor only; see above. } @addoption{BottomBackscatterDb, n, The seafloor backscattering strength at each frequency, dB. Default -25, -23, -22. } @addoption{NoiseSvAt1mDb, n, The noise floor referred to one metre range at each frequency, dB. Default -125, -128, -130. } @addoption{SvBiasDb, n, A calibration offset added to the reported Sv at each frequency, dB. Default 0 at every frequency. } @addoption{BinSize, 1, The range bin size of the echogram, m. Default 1. } @addoption{SvFloorDb, 1, The lowest Sv the file ever reports, dB. Default -140. } @addoption{IndependentSamplesPerBin, 1, The Gamma shape of the multiplicative speckle, that is the number of independent samples averaged in a bin. Default 8, about 1.5 dB. } @addoption{WriteFile, 1, One to write the echogram file, zero to run the altimeter half only. Default 1. } @addoption{FileName, 1, The echogram file, relative so that an FMU host controls placement. Default the object name followed by _echogram.csv. }
FlushEveryNPings 1 How often the echogram stream is flushed. Default 20. Zero never flushes before the destructor. @addoption{SvPrecision, 1, The number of decimals written for each Sv value. Default 2, which is well inside the model's own accuracy and roughly halves the file. } @addoption{NoiseSeed, 1, The random seed. Default 0, which means a perfect instrument: no range bias or noise, no acoustic noise floor and no speckle. } @addoption{BeamWidth, 1, The full opening angle of the beam drawn by the visualisation, degrees. Default 7. It is a drawing parameter and nothing else: the acoustic model casts a single ray along MountDirection and its negation, so no reported quantity depends on it. }

Initial conditions

This SimObject declares no states, so it takes no initial conditions.

This SimObject is referred to as Auv/Echosounder