|
FhSim
3.1.0
Marine systems simulation
|
In-situ silhouette camera counting and sizing plankton in a small imaged volume.
Collaboration diagram for Silhouette camera:A silhouette camera shadowgraphs a small, precisely known volume of water at a fixed frame rate and counts the particles it can segment out of each image. This class queries one named species concentration field of the shared Environment resource at the mounted sensor head, converts the concentration to an expected number of particles in the imaged volume, draws an actual count, gives every counted particle a size, and aggregates a fixed number of frames into one reported measurement.
It follows SensorCtd in every structural respect - a SensorFrame for the mounting geometry, a SampleClock for the frame rate, a SensorNoise for portable random draws, the field handle resolved once in FinalSetup - with one difference: it has no states. Nothing about a frame-counting instrument is a continuous-time quantity, so the aggregation counters are plain members written only in AcceptedStep, and OdeFcn is empty.
The sample volume is 0.040 x 0.055 x 0.065 = 1.43e-4 m^3. At the design Calanus peak of 2.2e-3 kg/m^3, which at 1.1e-6 kg per animal is 2000 individuals per m^3, the raw expected number of animals inside the imaged volume is
\[ n \cdot V = 2000 \cdot 1.43\cdot 10^{-4} = 0.286 \ \text{per frame} \]
at the default 4 Hz. Once the capture efficiency and the detection probability are applied the expectation the instrument actually draws against is \(\lambda = 0.286 \cdot 0.85 \cdot 0.92 = 0.224\) per frame. A single frame is therefore almost pure noise, and that is exactly why aggregation is not optional here.
FramesPerOutput = 40 is 10 s of imaging, about 15 m of track at a 1.5 m/s cruise, and gives \(\lambda_{window} = 8.9\) counts per reported measurement. The counting error of a Poisson variable is \(1/\sqrt{\lambda}\), so that is a 33 % one-sigma error on a single reported concentration. This is the trade a user of this model has to make consciously:
FramesPerOutput | Window | Track at 1.5 m/s | Counts | Counting error |
|---|---|---|---|---|
| 4 | 1 s | 1.5 m | 0.9 | 105 % |
| 40 | 10 s | 15 m | 8.9 | 33 % |
| 160 | 40 s | 60 m | 35.8 | 17 % |
| 640 | 160 s | 240 m | 143 | 8 % |
Halving the counting error costs four times the spatial averaging. No amount of post-processing recovers what the small volume never sampled, and a survey design that wants both fine spatial resolution and a precise concentration needs a bigger imaged volume, not a longer window.
Per frame, with \(n\) the number concentration at the sensor head:
\[ \lambda = n \cdot V \cdot \texttt{CaptureEfficiency} \cdot \texttt{DetectionProbability} \]
and the frame's count is \(k \sim \mathrm{Poisson}(\lambda)\) plus \(\mathrm{Poisson}(\texttt{FalsePositiveRate})\) spurious detections from marine snow, bubbles and sensor artefacts.
This is the dominant error of a small-volume imaging counter and it is not optional: no additive-Gaussian model reproduces it**, because a count has to be non-negative and integer and has to have a variance equal to its mean. At the design concentration the distribution of a single frame is not remotely symmetric - about 80 % of frames contain nothing at all - so a Gaussian would predict negative concentrations roughly as often as positive ones.
Each counted animal is given a length drawn from a lognormal whose median is the field's LengthM property and whose log-sigma is derived from the field's LengthStdM property, \(\sigma = \sqrt{\ln\left(1 + (\texttt{LengthStdM}/\texttt{LengthM})^2\right)}\), and the drawn length is then scaled by \((1 + N(0, \texttt{SizeErrorStdRelative}))\) to represent the segmentation and calibration error of the image analysis.
A false positive is not given a plausible animal length. It draws uniformly in the logarithm of the size across the histogram range, so false positives broaden the reported size distribution and fill its tails. That is the realistic failure mode of a silhouette camera in turbid or snow-laden water: the concentration is biased high and the size distribution loses its shape, and a user looking only at the mean size would not notice.
The reported number concentration divides the aggregated count by what the instrument believes* about itself:
\[ n_{reported} = \frac{\texttt{Count}} {N_{frames} \cdot V \cdot \texttt{CaptureEfficiency} \cdot \texttt{AssumedDetectionProbability}} \]
AssumedDetectionProbability defaults to DetectionProbability, in which case the instrument is perfectly calibrated and the conversion is unbiased. Setting it to a different value introduces a calibration bias of exactly DetectionProbability / AssumedDetectionProbability, which is the honest way to model the fact that no imaging counter knows its own detection probability: it is measured in a tank against a species and a turbidity that are never quite the ones in the water. Making the bias an explicit, tunable parameter is better than hiding it inside the detection probability, because a sensitivity study can then sweep it.
The mass concentration is NumberConcentration * IndividualMassKg, with the individual mass taken from the field's MassKg property unless IndividualMassKg is set.
NoiseSeed = 0 means deterministic and noise free, as on every AUV sensor here:
SizeMean is \(L\,e^{\sigma_t^2/2}\), SizeStd is \(\texttt{SizeMean}\sqrt{e^{\sigma_t^2}-1}\), and the histogram is that lognormal's probability in each bin. The approximation is exact in the limit of a small sizing error and is within 0.1 % of the true product moments at the default 8 %.See SensorNoise for why the distributions are hand rolled rather than taken from the standard library.
AcceptedStep runs
a **while and not an if**. The guidance classes in this library collapse several missed update periods into one decision, because a controller only cares about the present. A counting sensor is the opposite: every frame it fails to take is signal permanently thrown away, and the reported concentration would silently read low by the fraction of frames dropped. A long integrator step must therefore consume every frame that fell inside it.
When FramesPerOutput frames have accumulated the window closes, all nine output ports are written at once, Valid becomes 1 and the counters reset. Before the first window closes Valid is 0 and every other output is zero; a consumer must gate on Valid.
A window that happens to contain no particles at all reports a zero concentration, a zero SizeMean and SizeStd, and an all-zero histogram, because there is nothing to normalise. That is a real and common outcome at low concentrations, not an error, and it is another reason the reported concentration must be averaged over many windows before it means anything.
StepMax. SampleClock warns once if a frame is taken more than a quarter of a frame period late, which at the default 4 Hz means StepMax should be 0.0625 s or less.<FileOutput> interpolates linearly between its output times, so a logged trace of these held outputs ramps between windows rather than stepping. Log at TOutput equal to FramesPerOutput / FrameRate if the staircase matters.As on SensorCtd there are deliberately no TimeDelay, LossProbability or OutlierProbability parameters. fhsim_base's Signal/Samplifier models those, and the outports exist to be chained into one:
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 species field handle is resolved once, in FinalSetup. A field name that is not registered is a fatal error there, as on SensorCtd: a plankton camera wired to a species that does not exist is a configuration mistake, and reporting a plausible looking 0 kg/m^3 through a whole survey is worse than refusing to run. A missing MassKg property is equally fatal unless IndividualMassKg was given explicitly, because without it the mass concentration - the instrument's main output - cannot be formed at all.
A missing LengthM or LengthStdM is not fatal. Those only shape the size distribution, so one warning is logged and the sizing falls back to the geometric centre of the histogram range with no spread.
EnvironmentProvider::GetScalarValue is not const. It is only reached from AcceptedStep here, which is non-const, so the OdeFcn purity concern that applies to SensorCtd does not arise.| 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. } |
|---|
| Name | Width | Description @addoption{Concentration, 1, The measured mass concentration of the species, kg/m^3. The main output. Zero until the first window closes. } @addoption{NumberConcentration, 1, The measured number concentration, individuals per m^3. } @addoption{Count, 1, The raw aggregated particle count over the window, an integer valued double when the sensor is stochastic and a fractional expectation when it is deterministic. } @addoption{SizeMean, 1, The mean measured particle length over the window, m. Zero when the window counted nothing. } @addoption{SizeStd, 1, The standard deviation of the measured particle lengths over the window, m. } @addoption{SizeHistogram, NumSizeBins, The normalised size distribution over log spaced bins from SizeBinMin to SizeBinMax. Sums to one, or to zero when the window counted nothing. Lengths outside the range fall in the end bins. } @addoption{SampleTime, 1, The mid time of the window the held measurement came from, s. A measurement is georeferenced with this and with Position, not with the time it happens to be read. } @addoption{Position, 3, The mean NED position of the sensor head over the window, m. } @addoption{Valid, 1, One once the first window has closed, zero before that. A consumer must gate on this. } |
|---|
| Name | Width | Description |
|---|---|---|
| SpeciesFieldName | 1 | The name of the registered species concentration field to count. Default Calanus. A name that is not registered is a fatal error. @addoption{MountOffset, 3, The sensor head position in the body frame, m. Default 0 0 0. } @addoption{MountDirection, 3, The beam direction in the body frame. A silhouette camera images a fixed volume rather than a beam, so this is unused and is only read because the SensorFrame is shared with the sensors that do have one. } @addoption{FrameRate, 1, The imaging rate, Hz. Default 4. } @addoption{FramesPerOutput, 1, The number of frames aggregated into one reported measurement. Default 40, that is a 10 s window. See the table in the description for the precision this buys. } @addoption{SampleVolume, 3, The imaged volume as its three side lengths, m. Default 0.040 0.055 0.065, whose product is 1.43e-4 m^3. } |
| CaptureEfficiency | 1 | The fraction of the animals present in the imaged volume that are actually carried through it and imaged. Default 0.85. |
| DetectionProbability | 1 | The probability that an imaged animal is segmented and counted. Default 0.92. This is the true value the physics uses. @addoption{AssumedDetectionProbability, 1, The detection probability the instrument believes it has and divides by. Defaults to DetectionProbability, which is an unbiased instrument. A different value introduces a calibration bias of exactly DetectionProbability over AssumedDetectionProbability. } @addoption{FalsePositiveRate, 1, The expected number of spurious detections per frame, from marine snow, bubbles and artefacts. Default 0.05. Poisson distributed and sized uniformly in log size across the histogram range. } @addoption{IndividualMassKg, 1, The mass of one animal, kg. Default 0, which means take the MassKg property from the species field. A field with no MassKg property and no explicit value here is a fatal error. } |
| SizeErrorStdRelative | 1 | The relative standard deviation of the per particle sizing error. Default 0.08. @addoption{NumSizeBins, 1, The number of size histogram bins, which is also the width of the SizeHistogram port. Default 8. } @addoption{SizeBinMin, 1, The lower edge of the first size bin, m. Default 0.0005. } @addoption{SizeBinMax, 1, The upper edge of the last size bin, m. Default 0.0060. } @addoption{NoiseSeed, 1, The random seed. Default 0, which means deterministic and noise free with an exact fractional count and an analytic size distribution. Any other value seeds the generator and enables the Poisson counting, the false positives and the per particle sizing. } |
This SimObject has no states and therefore no initial conditions.
This SimObject is referred to as Auv/SilCam