Statistics
- Max magnitude: @Model.MaxMagnitude
- Max magnitude outside hum range: @Model.MaxNonHumMagnitude
- Total magnitude outside hum range: @Model.TotalNonHumMagnitude
- Total magnitude of hum range: @Model.TotalHumMagnitude
- Signal ratio: @Model.SignalRatio%
- Status: @Model.Status
+ # Channels: @Model.ChannelCount
+
+
Summary
+
+ Max magnitude: @Model.MaxMagnitude
+ Max magnitude outside hum range: @Model.MaxNonHumMagnitude
+ Total magnitude outside hum range: @Model.TotalNonHumMagnitude
+ Total magnitude of hum range: @Model.TotalHumMagnitude
+ Signal ratio: @Model.SignalRatio %
+ Status: @Model.Status
+
+ @if (Model.ChannelCount > 1)
+ {
+ @for (int i = 0; i < Model.ChannelCount; i++)
+ {
+
Channel @(i + 1):
+
+ Max magnitude: @Model.GetMaxMagnitude(i)
+ Max magnitude outside hum range: @Model.GetMaxNonHumMagnitude(i)
+ Total magnitude outside hum range: @Model.GetTotalNonHumMagnitude(i)
+ Total magnitude of hum range: @Model.GetTotalHumMagnitude(i)
+ Signal ratio: @Model.GetSignalRatio(i) %
+ Status: @Model.GetStatus(i)
+
+ }
+ }
+
diff --git a/OrcanodeMonitor/Pages/SpectralDensity.cshtml.cs b/OrcanodeMonitor/Pages/SpectralDensity.cshtml.cs
index 2dad1e7..c9d8ac3 100644
--- a/OrcanodeMonitor/Pages/SpectralDensity.cshtml.cs
+++ b/OrcanodeMonitor/Pages/SpectralDensity.cshtml.cs
@@ -1,9 +1,11 @@
// Copyright (c) Orcanode Monitor contributors
// SPDX-License-Identifier: MIT
using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.CodeAnalysis;
using OrcanodeMonitor.Core;
using OrcanodeMonitor.Data;
using OrcanodeMonitor.Models;
+using System.Text.Json;
using static OrcanodeMonitor.Core.Fetcher;
namespace OrcanodeMonitor.Pages
@@ -21,15 +23,15 @@ public class SpectralDensityModel : PageModel
private Orcanode? _node = null;
public string NodeName => _node?.DisplayName ?? "Unknown";
private List
_labels;
- private List _maxBucketMagnitude;
public List Labels => _labels;
- public List MaxBucketMagnitude => _maxBucketMagnitude;
public string AudioUrl => _event?.Url ?? "Unknown";
public int MaxMagnitude { get; private set; }
+ public int ChannelCount { get; private set; }
public int TotalNonHumMagnitude => (int)Math.Round(_totalNonHumMagnitude);
public int TotalHumMagnitude => (int)Math.Round(_totalHumMagnitude);
private double _totalHumMagnitude;
private double _totalNonHumMagnitude;
+ private FrequencyInfo? _frequencyInfo = null;
public int MaxNonHumMagnitude { get; private set; }
public int SignalRatio { get; private set; }
public string Status { get; private set; }
@@ -44,54 +46,176 @@ public SpectralDensityModel(OrcanodeMonitorContext context, ILogger();
- _maxBucketMagnitude = new List();
LastModified = string.Empty;
}
- private void UpdateFrequencyInfo(FrequencyInfo frequencyInfo)
+ ///
+ /// Maximum frequency to analyze in Hz.
+ /// Typical human hearing range is up to 20kHz.
+ /// Orca calls are up to 40kHz.
+ ///
+ private const int MAX_FREQUENCY = 24000;
+
+ ///
+ /// Number of points to plot on the graph. 1000 points provides a good balance
+ /// between resolution and performance.
+ ///
+ private const int POINT_COUNT = 1000;
+
+ private void FillInGraphPoints(List labels, List maxBucketMagnitudeList, int? channel = null)
{
- const int MaxFrequency = 24000;
- const int PointCount = 1000;
+ if (_frequencyInfo == null)
+ {
+ return;
+ }
// Compute the logarithmic base needed to get PointCount points.
- double b = Math.Pow(MaxFrequency, 1.0 / PointCount);
+ double b = Math.Pow(MAX_FREQUENCY, 1.0 / POINT_COUNT);
double logb = Math.Log(b);
- double maxMagnitude = frequencyInfo.MaxMagnitude;
- var maxBucketMagnitude = new double[PointCount];
- var maxBucketFrequency = new int[PointCount];
-
- foreach (var pair in frequencyInfo.FrequencyMagnitudes)
+ var maxBucketMagnitude = new double[POINT_COUNT];
+ var maxBucketFrequency = new int[POINT_COUNT];
+ foreach (var pair in _frequencyInfo.GetFrequencyMagnitudes(channel))
{
double frequency = pair.Key;
double magnitude = pair.Value;
- int bucket = (frequency < 1) ? 0 : Math.Min(PointCount - 1, (int)(Math.Log(frequency) / logb));
+ int bucket = (frequency < 1) ? 0 : Math.Min(POINT_COUNT - 1, (int)(Math.Log(frequency) / logb));
if (maxBucketMagnitude[bucket] < magnitude)
{
maxBucketMagnitude[bucket] = magnitude;
maxBucketFrequency[bucket] = (int)Math.Round(frequency);
}
}
-
- // Fill in graph points.
- for (int i = 0; i < PointCount; i++)
+ for (int i = 0; i < POINT_COUNT; i++)
{
if (maxBucketMagnitude[i] > 0)
{
- _labels.Add(maxBucketFrequency[i].ToString());
- _maxBucketMagnitude.Add(maxBucketMagnitude[i]);
+ labels.Add(maxBucketFrequency[i].ToString());
+ maxBucketMagnitudeList.Add(maxBucketMagnitude[i]);
}
}
+ }
- double maxNonHumMagnitude = frequencyInfo.GetMaxNonHumMagnitude();
- MaxMagnitude = (int)Math.Round(maxMagnitude);
- MaxNonHumMagnitude = (int)Math.Round(maxNonHumMagnitude);
- Status = Orcanode.GetStatusString(frequencyInfo.Status);
- _totalHumMagnitude = frequencyInfo.GetTotalHumMagnitude();
- _totalNonHumMagnitude = frequencyInfo.GetTotalNonHumMagnitude();
- SignalRatio = (int)Math.Round(100 * _totalNonHumMagnitude / _totalHumMagnitude);
+ private double GetBucketMagnitude(string label, List labels, List magnitudes)
+ {
+ double sum = 0;
+ for (int i = 0; i < labels.Count; i++)
+ {
+ if (labels[i] == label)
+ {
+ sum += magnitudes[i];
+ }
+ }
+ return sum;
}
+ private void UpdateFrequencyInfo()
+ {
+ if (_frequencyInfo == null)
+ {
+ return;
+ }
+
+ // Compute graph points.
+ var summaryLabels = new List();
+ var summaryMaxBucketMagnitude = new List();
+ FillInGraphPoints(summaryLabels, summaryMaxBucketMagnitude);
+ var channelLabels = new List[_frequencyInfo.ChannelCount];
+ var channelMaxBucketMagnitude = new List[_frequencyInfo.ChannelCount];
+ for (int i = 0; i < _frequencyInfo.ChannelCount; i++)
+ {
+ channelLabels[i] = new List();
+ channelMaxBucketMagnitude[i] = new List();
+ FillInGraphPoints(channelLabels[i], channelMaxBucketMagnitude[i], i);
+ }
+
+ // Collect all labels.
+ var mainLabels = new HashSet(summaryLabels);
+ for (int i = 0; i < _frequencyInfo.ChannelCount; i++)
+ {
+ mainLabels.UnionWith(channelLabels[i]);
+ }
+ _labels = mainLabels.ToList();
+
+ // Align data.
+ var summaryDataset = _labels.Select(label => new
+ {
+ x = label,
+ y = summaryLabels.Contains(label) ? GetBucketMagnitude(label, summaryLabels, summaryMaxBucketMagnitude) : (double?)null
+ }).ToList