Measure tool

This commit is contained in:
Agustín Gimenez 2023-02-03 12:04:36 +01:00
parent 397c406085
commit 767cd0314b
8 changed files with 296 additions and 3 deletions

View File

@ -0,0 +1,30 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="375" d:DesignHeight="260" Width="375" Height="260"
x:Class="LogicAnalyzer.Controls.ChannelMeasures" BorderBrush="LightGray" BorderThickness="1">
<Grid ColumnDefinitions="3.2*,*" RowDefinitions="20,*">
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="10,5,10,10" Name="lblName">Channel 0</TextBlock>
<StackPanel Grid.Column="0" Grid.Row="1" Spacing="10" Margin="10,10,10,10">
<TextBlock>Total positive pulses:</TextBlock>
<TextBlock>Average positive pulse duration:</TextBlock>
<TextBlock>Predominant positive pulse duration:</TextBlock>
<TextBlock>Total negative pulses:</TextBlock>
<TextBlock>Average negative pulse duration:</TextBlock>
<TextBlock>Predominant negative pulse duration:</TextBlock>
<TextBlock>Average frequency:</TextBlock>
<TextBlock>Predominant frequency:</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="1" Spacing="10" Margin="10,10,10,10">
<TextBlock Name="lblPosPulses">0</TextBlock>
<TextBlock Name="lblPosPulsesDuration">0ns</TextBlock>
<TextBlock Name="lblPosPredPulsesDuration">0ns</TextBlock>
<TextBlock Name="lblNegPulses">0</TextBlock>
<TextBlock Name="lblNegPulsesDuration">0ns</TextBlock>
<TextBlock Name="lblNegPredPulsesDuration">0ns</TextBlock>
<TextBlock Name="lblFreq">0Hz</TextBlock>
<TextBlock Name="lblPredFreq">0hz</TextBlock>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,122 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using LogicAnalyzer.Classes;
using LogicAnalyzer.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
namespace LogicAnalyzer.Controls
{
public partial class ChannelMeasures : UserControl
{
public static readonly StyledProperty<IBrush> ForegroundProperty = AvaloniaProperty.Register<SampleMarker, IBrush>(nameof(Foreground));
public static readonly StyledProperty<IBrush> BackgroundProperty = AvaloniaProperty.Register<SampleMarker, IBrush>(nameof(Background));
public new IBrush Foreground
{
get { return GetValue<IBrush>(ForegroundProperty); }
set { SetValue<IBrush>(ForegroundProperty, value); base.Foreground = value; UpdateTextColors(); InvalidateVisual(); }
}
public new IBrush Background
{
get { return GetValue<IBrush>(BackgroundProperty); }
set { SetValue<IBrush>(BackgroundProperty, value); base.Background = value; UpdateTextColors(); InvalidateVisual(); }
}
void UpdateTextColors()
{
lblFreq.Foreground= Foreground;
lblFreq.Background= Background;
lblNegPulses.Foreground= Foreground;
lblNegPulses.Background= Background;
lblNegPulsesDuration.Foreground= Foreground;
lblNegPulsesDuration.Background= Background;
lblPosPulses.Foreground= Foreground;
lblPosPulses.Background = Background;
lblPosPulsesDuration.Foreground = Foreground;
lblPosPulsesDuration.Background = Background;
}
public ChannelMeasures()
{
InitializeComponent();
}
public void SetData(string channelName, byte[] channelSamples, int frequency)
{
int currentPulse = -1;
int currentCount = 0;
List<int> posLengths= new List<int>();
List<int> negLengths = new List<int>();
for (int buc = 0; buc < channelSamples.Length; buc++)
{
if (channelSamples[buc] != currentPulse)
{
if (currentPulse == 1)
posLengths.Add(currentCount);
else if (currentPulse == 0)
negLengths.Add(currentCount);
currentPulse = channelSamples[buc];
currentCount = 1;
}
else
currentCount++;
}
if (currentPulse == 1)
posLengths.Add(currentCount);
else if (currentPulse == 0)
negLengths.Add(currentCount);
var posGrouped = posLengths.GroupBy(p => p).OrderBy(g => g.Count()).ToArray();
var posOrderedByCount = posGrouped.SelectMany(g => g.ToArray()).ToArray();
int fivePercent = (int)(posOrderedByCount.Length * 0.95);
var finalPosSamples = posOrderedByCount.Skip(fivePercent).ToArray();
var negGrouped = negLengths.GroupBy(p => p).OrderBy(g => g.Count()).ToArray();
var negOrderedByCount = negGrouped.SelectMany(g => g.ToArray()).ToArray();
fivePercent = (int)(negOrderedByCount.Length * 0.95);
var finalNegSamples = negOrderedByCount.Skip(fivePercent).ToArray();
int minPulses = Math.Min(finalPosSamples.Length, finalNegSamples.Length);
var matchedPos = finalPosSamples.Skip(finalPosSamples.Length - minPulses).ToArray();
var matchedNeg = finalNegSamples.Skip(finalNegSamples.Length - minPulses).ToArray();
int totalSamples = matchedPos.Sum() + matchedNeg.Sum();
double period = totalSamples * (1.0 / frequency);
double predPosPeriod = finalPosSamples.Length == 0 ? 0 : (finalPosSamples.Average() * (1.0 / frequency));
double predNegPeriod = finalNegSamples.Length == 0 ? 0 : (finalNegSamples.Average() * (1.0 / frequency));
double predFreq = period == 0 ? 0 : (1.0 / (predPosPeriod + predNegPeriod));
double avgPosPeriod = posLengths.Count == 0 ? 0 : (posLengths.Average() * (1.0 / frequency));
double avgNegPeriod = negLengths.Count == 0 ? 0 : (negLengths.Average() * (1.0 / frequency));
double avgFreq = period == 0 ? 0 : (1.0 / (avgPosPeriod + avgNegPeriod));
lblPosPulses.Text = posLengths.Count.ToString();
lblNegPulses.Text = negLengths.Count.ToString();
lblPosPredPulsesDuration.Text = predPosPeriod.ToSmallTime();
lblPosPulsesDuration.Text = avgPosPeriod.ToSmallTime();
lblNegPredPulsesDuration.Text = predNegPeriod.ToSmallTime();
lblNegPulsesDuration.Text = avgNegPeriod.ToSmallTime();
lblPredFreq.Text = predFreq.ToLargeFrequency();
lblFreq.Text = avgFreq.ToLargeFrequency();
lblName.Text = $"Channel {channelName}";
}
}
}

View File

@ -13,6 +13,7 @@
<ContextMenu Name="rgnDeleteMenu"> <ContextMenu Name="rgnDeleteMenu">
<MenuItem Header="Delete regions" Name="mnuDeleteRegions" /> <MenuItem Header="Delete regions" Name="mnuDeleteRegions" />
<MenuItem Header="Delete regions and samples" Name="mnuDeleteRegionsSamples" /> <MenuItem Header="Delete regions and samples" Name="mnuDeleteRegionsSamples" />
<MenuItem Header="Measure region" Name="mnuMeasure" />
</ContextMenu> </ContextMenu>
</Panel.ContextMenu> </Panel.ContextMenu>
</Panel> </Panel>

View File

@ -52,6 +52,7 @@ namespace LogicAnalyzer.Controls
public event EventHandler<SamplesEventArgs> SamplesDeleted; public event EventHandler<SamplesEventArgs> SamplesDeleted;
public event EventHandler<UserMarkerEventArgs> UserMarkerSelected; public event EventHandler<UserMarkerEventArgs> UserMarkerSelected;
public event EventHandler<SamplesEventArgs> MeasureSamples;
List<SelectedSampleRegion> regions = new List<SelectedSampleRegion>(); List<SelectedSampleRegion> regions = new List<SelectedSampleRegion>();
@ -66,6 +67,13 @@ namespace LogicAnalyzer.Controls
InitializeComponent(); InitializeComponent();
mnuDeleteRegions.Click += MnuDeleteRegions_Click; mnuDeleteRegions.Click += MnuDeleteRegions_Click;
mnuDeleteRegionsSamples.Click += MnuDeleteRegionsSamples_Click; mnuDeleteRegionsSamples.Click += MnuDeleteRegionsSamples_Click;
mnuMeasure.Click += MnuMeasure_Click;
}
private void MnuMeasure_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (MeasureSamples != null)
MeasureSamples(this, new SamplesEventArgs { FirstSample = regionsToDelete[0].FirstSample, SampleCount = regionsToDelete[0].SampleCount });
} }
private void MnuDeleteRegionsSamples_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) private void MnuDeleteRegionsSamples_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)

View File

@ -0,0 +1,31 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="LogicAnalyzer.Dialogs.MeasureDialog"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="450"
MaxWidth="400" MinWidth="400"
MaxHeight="450" MinHeight="450"
Title="Measurements" Icon="/Assets/window.ico"
Background="#383838" CanResize="False" WindowStartupLocation="CenterOwner">
<StackPanel>
<Grid ColumnDefinitions="2.5*,*" RowDefinitions="20,20,20">
<TextBlock Grid.Row="0" Margin="10,5,10,5">Total samples:</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="10,5,10,5" Name="lblSamples">0</TextBlock>
<TextBlock Grid.Row="1" Margin="10,5,10,5">Total period:</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="1" Margin="10,5,10,5" Name="lblPeriod">0ns</TextBlock>
<TextBlock Grid.Row="2" Margin="10,5,10,5">Sample period:</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="1" Margin="10,5,10,5" Name="lblSamplePeriod">0ns</TextBlock>
</Grid>
<Grid RowDefinitions="8*,2*">
<ScrollViewer Name="scrViewer" Margin="10" VerticalAlignment="Top" Height="300" Background="#282828">
<StackPanel Name="pnlControls">
</StackPanel>
</ScrollViewer>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.Row="1" Margin="0,10,0,10">
<Button Name="btnAccept" Margin="10,0,10,0">Accept</Button>
</StackPanel>
</Grid>
</StackPanel>
</Window>

View File

@ -0,0 +1,42 @@
using Avalonia.Controls;
using LogicAnalyzer.Controls;
using LogicAnalyzer.Extensions;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace LogicAnalyzer.Dialogs
{
public partial class MeasureDialog : Window
{
public MeasureDialog()
{
InitializeComponent();
btnAccept.Click += BtnAccept_Click;
}
private void BtnAccept_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
this.Close();
}
public void SetData(string[] ChannelNames, IEnumerable<byte[]> Samples, int SamplingFrequency)
{
int samples = Samples.First().Length;
lblSamples.Text = samples.ToString();
double period = (1.0 / (double)SamplingFrequency) * samples;
lblPeriod.Text = period.ToSmallTime();
period = 1.0 / (double)SamplingFrequency;
lblSamplePeriod.Text = period.ToSmallTime();
for (int buc = 0; buc < ChannelNames.Length; buc++)
{
ChannelMeasures cm = new ChannelMeasures();
cm.SetData(ChannelNames[buc], Samples.Skip(buc).First(), SamplingFrequency);
pnlControls.Children.Add(cm);
}
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LogicAnalyzer.Extensions
{
public static class DoubleExtensions
{
public static string ToSmallTime(this double Time)
{
if (Time < 0.000001)
return $"{Math.Round(Time * 1000000000, 2)}ns";
else if (Time < 0.001)
return $"{Math.Round(Time * 1000000, 2)}us";
else if (Time < 1)
return $"{Math.Round(Time * 1000, 2)}ms";
else
return $"{Math.Round(Time, 2)}s";
}
public static string ToLargeFrequency(this double Frequency)
{
if (Frequency > 999999)
return $"{Math.Round(Frequency / 1000000, 2)}Mhz";
else if (Frequency > 999)
return $"{Math.Round(Frequency / 1000, 2)}Khz";
else
return $"{Math.Round(Frequency, 2)}Hz";
}
}
}

View File

@ -42,6 +42,7 @@ namespace LogicAnalyzer
sampleMarker.RegionDeleted += sampleMarker_RegionDeleted; sampleMarker.RegionDeleted += sampleMarker_RegionDeleted;
sampleMarker.UserMarkerSelected += sampleMarker_UserMarkerSelected; sampleMarker.UserMarkerSelected += sampleMarker_UserMarkerSelected;
sampleMarker.SamplesDeleted += SampleMarker_SamplesDeleted; sampleMarker.SamplesDeleted += SampleMarker_SamplesDeleted;
sampleMarker.MeasureSamples += SampleMarker_MeasureSamples;
tkInScreen.PropertyChanged += tkInScreen_ValueChanged; tkInScreen.PropertyChanged += tkInScreen_ValueChanged;
scrSamplePos.Scroll += scrSamplePos_ValueChanged; scrSamplePos.Scroll += scrSamplePos_ValueChanged;
mnuOpen.Click += mnuOpen_Click; mnuOpen.Click += mnuOpen_Click;
@ -53,6 +54,25 @@ namespace LogicAnalyzer
RefreshPorts(); RefreshPorts();
} }
private async void SampleMarker_MeasureSamples(object? sender, SamplesEventArgs e)
{
List<byte[]> samples = new List<byte[]>();
for (int buc = 0; buc < sampleViewer.ChannelCount; buc++)
samples.Add(ExtractSamples(buc, sampleViewer.Samples, e.FirstSample, e.SampleCount));
var names = channelViewer.ChannelsText.ToArray();
for (int buc = 0; buc < names.Length; buc++)
if (string.IsNullOrWhiteSpace(names[buc]))
names[buc] = (buc + 1).ToString();
MeasureDialog dlg = new MeasureDialog();
dlg.SetData(names, samples, settings.Frequency);
await dlg.ShowDialog(this);
}
private async void MnuNetSettings_Click(object? sender, RoutedEventArgs e) private async void MnuNetSettings_Click(object? sender, RoutedEventArgs e)
{ {
var dlg = new NetworkSettingsDialog(); var dlg = new NetworkSettingsDialog();
@ -370,6 +390,12 @@ namespace LogicAnalyzer
channel.Samples = samples.Select(s => (s & mask) != 0 ? (byte)1 : (byte)0).ToArray(); channel.Samples = samples.Select(s => (s & mask) != 0 ? (byte)1 : (byte)0).ToArray();
} }
private byte[] ExtractSamples(int channel, uint[] samples, int firstSample, int count)
{
int mask = 1 << channel;
return samples.Skip(firstSample).Take(count).Select(s => (s & mask) != 0 ? (byte)1 : (byte)0).ToArray();
}
private async void btnOpenClose_Click(object? sender, EventArgs e) private async void btnOpenClose_Click(object? sender, EventArgs e)
{ {
if (driver == null) if (driver == null)