Skip to content

Commit

Permalink
Allow getting spectral density for a specific hydrophone stream event
Browse files Browse the repository at this point in the history
Fixes #229

Signed-off-by: Dave Thaler <[email protected]>
  • Loading branch information
dthaler committed Dec 26, 2024
1 parent 33e0640 commit b595a3a
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 55 deletions.
23 changes: 15 additions & 8 deletions OrcanodeMonitor/Core/Fetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.CodeAnalysis.Elfie.Diagnostics;

namespace OrcanodeMonitor.Core
{
Expand Down Expand Up @@ -870,9 +871,9 @@ private static List<OrcanodeEvent> AddOlderEvent(List<OrcanodeEvent> orcanodeEve
}
}

public static void AddOrcanodeEvent(OrcanodeMonitorContext context, Orcanode node, string type, string value)
public static void AddOrcanodeEvent(OrcanodeMonitorContext context, Orcanode node, string type, string value, string? url = null)
{
var orcanodeEvent = new OrcanodeEvent(node, type, value, DateTime.UtcNow);
var orcanodeEvent = new OrcanodeEvent(node, type, value, DateTime.UtcNow, url);
context.OrcanodeEvents.Add(orcanodeEvent);
}

Expand All @@ -894,16 +895,14 @@ private static void AddDiskCapacityChangeEvent(OrcanodeMonitorContext context, O
AddOrcanodeEvent(context, node, OrcanodeEventTypes.SDCardSize, value);
}

private static void AddHydrophoneStreamStatusEvent(OrcanodeMonitorContext context, Orcanode node)
private static void AddHydrophoneStreamStatusEvent(OrcanodeMonitorContext context, Orcanode node, string? url)
{
string value = node.OrcasoundOnlineStatusString;
AddOrcanodeEvent(context, node, OrcanodeEventTypes.HydrophoneStream, value);
AddOrcanodeEvent(context, node, OrcanodeEventTypes.HydrophoneStream, value, url);
}

public async static Task<FrequencyInfo?> GetLatestAudioSampleAsync(Orcanode node, string unixTimestampString, bool updateNode, ILogger logger)
{
OrcanodeOnlineStatus oldStatus = node.S3StreamStatus;

string url = "https://" + node.S3Bucket + ".s3.amazonaws.com/" + node.S3NodeName + "/hls/" + unixTimestampString + "/live.m3u8";
using HttpResponseMessage response = await _httpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
Expand Down Expand Up @@ -947,10 +946,18 @@ private static void AddHydrophoneStreamStatusEvent(OrcanodeMonitorContext contex
lineNumber = (lineNumber > 3) ? lineNumber - 3 : lineNumber - 1;
string lastLine = lines[lineNumber];
Uri newUri = new Uri(baseUri, lastLine);
return await GetExactAudioSampleAsync(node, newUri, logger);
}

public async static Task<FrequencyInfo?> GetExactAudioSampleAsync(Orcanode node, Uri uri, ILogger logger)
{
OrcanodeOnlineStatus oldStatus = node.S3StreamStatus;

try
{
using Stream stream = await _httpClient.GetStreamAsync(newUri);
using Stream stream = await _httpClient.GetStreamAsync(uri);
FrequencyInfo frequencyInfo = await FfmpegCoreAnalyzer.AnalyzeAudioStreamAsync(stream, oldStatus);
frequencyInfo.Url = uri.AbsoluteUri;
return frequencyInfo;
}
catch (Exception ex)
Expand Down Expand Up @@ -984,7 +991,7 @@ public async static Task UpdateManifestTimestampAsync(OrcanodeMonitorContext con
OrcanodeOnlineStatus newStatus = node.S3StreamStatus;
if (newStatus != oldStatus)
{
AddHydrophoneStreamStatusEvent(context, node);
AddHydrophoneStreamStatusEvent(context, node, frequencyInfo?.Url);
}
}

Expand Down
1 change: 1 addition & 0 deletions OrcanodeMonitor/Core/FfmpegCoreAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ private static double MinSignalRatio

public Dictionary<double, double> FrequencyMagnitudes { get; }
public OrcanodeOnlineStatus Status { get; }
public string Url { get; set; } = string.Empty;
public double MaxMagnitude => FrequencyMagnitudes.Values.Max();

// Microphone audio hum typically falls within the 50 Hz or 60 Hz
Expand Down
2 changes: 1 addition & 1 deletion OrcanodeMonitor/Core/MezmoFetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ public async static Task UpdateMezmoDataAsync(OrcanodeMonitorContext context, IL
private static void AddMezmoStatusEvent(OrcanodeMonitorContext context, Orcanode node)
{
string value = node.MezmoStatus.ToString();
Fetcher.AddOrcanodeEvent(context, node, OrcanodeEventTypes.MezmoLogging, value);
Fetcher.AddOrcanodeEvent(context, node, OrcanodeEventTypes.MezmoLogging, value, string.Empty);
}
}
}
6 changes: 5 additions & 1 deletion OrcanodeMonitor/Models/OrcanodeEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ public OrcanodeEvent()
ID = string.Empty;
DateTimeUtc = DateTime.UtcNow;
Year = DateTimeUtc.Year;
Url = string.Empty;
}

public OrcanodeEvent(Orcanode node, string type, string value, DateTime timestamp)
public OrcanodeEvent(Orcanode node, string type, string value, DateTime timestamp, string? url)
{
Slug = node.OrcasoundSlug;
Type = type;
Expand All @@ -89,6 +90,7 @@ public OrcanodeEvent(Orcanode node, string type, string value, DateTime timestam
OrcanodeId = node.ID;
Year = timestamp.Year;
ID = Guid.NewGuid().ToString();
Url = url ?? string.Empty;
}

#region persisted
Expand Down Expand Up @@ -118,6 +120,8 @@ public OrcanodeEvent(Orcanode node, string type, string value, DateTime timestam

public int Year { get; set; }

public string Url { get; set; }

#endregion persisted

#region derived
Expand Down
19 changes: 17 additions & 2 deletions OrcanodeMonitor/Pages/NodeEvents.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
<thead>
<tr>
<th>Timestamp (Pacific)</th>
<th>Event</th>
<th align="left">Event</th>
<th></th>
</tr>
</thead>
<tbody>
Expand All @@ -62,7 +63,21 @@
{
<tr class="@Model.GetEventClasses(item)">
<td>@item.DateTimeLocal.ToString("g")</td>
<td align="left">@item.Description</td>
<td align="left">
@if (!string.IsNullOrEmpty(item.Url))
{
<a href="@item.Url">@item.Description</a>
}
else
{
<span>@item.Description</span>
}
</td>
<td>
<button onclick="location.href='@Url.Page("/SpectralDensity", new { id = item.ID })'" class="btn selected" style="@Model.GetEventButtonStyle(item)">
View Spectral Density
</button>
</td>
</tr>
}
</tbody>
Expand Down
9 changes: 9 additions & 0 deletions OrcanodeMonitor/Pages/NodeEvents.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,14 @@ public string GetEventClasses(OrcanodeEvent item)
string classes = GetTypeClass(item) + " " + GetTimeRangeClass(item);
return classes;
}

public string GetEventButtonStyle(OrcanodeEvent item)
{
if ((item.Type == OrcanodeEventTypes.HydrophoneStream) && (item.Url != null))
{
return "display: inline-block;";
}
return "display: none;";
}
}
}
127 changes: 85 additions & 42 deletions OrcanodeMonitor/Pages/SpectralDensity.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ public class SpectralDensityModel : PageModel
{
private readonly OrcanodeMonitorContext _databaseContext;
private readonly ILogger<SpectralDensityModel> _logger;
private string _eventId;
private string _nodeId;
public string Id => _nodeId;
public string NodeId => _nodeId;
public string EventId => _eventId;
public string NodeName { get; private set; }
private List<string> _labels;
private List<double> _maxBucketMagnitude;
Expand All @@ -34,10 +36,57 @@ public SpectralDensityModel(OrcanodeMonitorContext context, ILogger<SpectralDens
_databaseContext = context;
_logger = logger;
_nodeId = string.Empty;
_eventId = string.Empty;
NodeName = "Unknown";
Status = string.Empty;
_labels = new List<string>();
_maxBucketMagnitude = new List<double>();
}

private async Task UpdateFrequencyDataAsync()
private void UpdateFrequencyInfo(FrequencyInfo frequencyInfo)
{
const int MaxFrequency = 24000;
const int PointCount = 1000;

// Compute the logarithmic base needed to get PointCount points.
double b = Math.Pow(MaxFrequency, 1.0 / PointCount);
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)
{
double frequency = pair.Key;
double magnitude = pair.Value;
int bucket = (frequency < 1) ? 0 : (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++)
{
if (maxBucketMagnitude[i] > 0)
{
_labels.Add(maxBucketFrequency[i].ToString());
_maxBucketMagnitude.Add(maxBucketMagnitude[i]);
}
}

double maxNonHumMagnitude = frequencyInfo.GetMaxNonHumMagnitude();
MaxMagnitude = (int)Math.Round(maxMagnitude);
MaxNonHumMagnitude = (int)Math.Round(maxNonHumMagnitude);
SignalRatio = (int)Math.Round(100 * maxNonHumMagnitude / maxMagnitude);
Status = Orcanode.GetStatusString(frequencyInfo.Status);
}

#if false
private async Task UpdateNodeFrequencyDataAsync()
{
_labels = new List<string> { };
_maxBucketMagnitude = new List<double> { };
Expand All @@ -54,52 +103,46 @@ private async Task UpdateFrequencyDataAsync()
FrequencyInfo? frequencyInfo = await Fetcher.GetLatestAudioSampleAsync(node, result.UnixTimestampString, false, _logger);
if (frequencyInfo != null)
{
const int MaxFrequency = 24000;
const int PointCount = 1000;

// Compute the logarithmic base needed to get PointCount points.
double b = Math.Pow(MaxFrequency, 1.0 / PointCount);
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)
{
double frequency = pair.Key;
double magnitude = pair.Value;
int bucket = (frequency < 1) ? 0 : (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++)
{
if (maxBucketMagnitude[i] > 0)
{
_labels.Add(maxBucketFrequency[i].ToString());
_maxBucketMagnitude.Add(maxBucketMagnitude[i]);
}
}

double maxNonHumMagnitude = frequencyInfo.GetMaxNonHumMagnitude();
MaxMagnitude = (int)Math.Round(maxMagnitude);
MaxNonHumMagnitude = (int)Math.Round(maxNonHumMagnitude);
SignalRatio = (int)Math.Round(100 * maxNonHumMagnitude / maxMagnitude);
Status = Orcanode.GetStatusString(frequencyInfo.Status);
UpdateFrequencyInfo(frequencyInfo);
}
}
}
#endif

private async Task UpdateEventFrequencyDataAsync()
{
_labels = new List<string> { };
_maxBucketMagnitude = new List<double> { };
OrcanodeEvent? e = _databaseContext.OrcanodeEvents.Where(e => e.ID == _eventId).FirstOrDefault();
if (e == null)
{
_logger.LogWarning("Node not found with ID: {EventId}", _eventId);
return;
}
Orcanode? node = e.Orcanode;
if (node == null)
{
_logger.LogWarning("Node not found with ID: {NodeId}", _nodeId);
return;
}
NodeName = node.DisplayName;
Uri? uri;
if (!Uri.TryCreate(e.Url, UriKind.Absolute, out uri) || (uri == null))
{
_logger.LogWarning("URI not found with event ID: {EventID}", _eventId);
return;
}
FrequencyInfo? frequencyInfo = await Fetcher.GetExactAudioSampleAsync(node, uri, _logger);
if (frequencyInfo != null)
{
UpdateFrequencyInfo(frequencyInfo);
}
}

public async Task OnGetAsync(string id)
{
_nodeId = id;
await UpdateFrequencyDataAsync();
_eventId = id;
await UpdateEventFrequencyDataAsync();
}
}
}
2 changes: 1 addition & 1 deletion TransferData/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private static async Task TransferDataAsync(OrcanodeMonitorContext fromContext,
{
continue;
}
OrcanodeEvent toEvent = new OrcanodeEvent(toNode, fromItem.Type, fromItem.Value, fromItem.DateTimeUtc);
OrcanodeEvent toEvent = new OrcanodeEvent(toNode, fromItem.Type, fromItem.Value, fromItem.DateTimeUtc, fromItem.Url);
toContext.OrcanodeEvents.Add(toEvent);
}
await toContext.SaveChangesAsync();
Expand Down

0 comments on commit b595a3a

Please sign in to comment.