Skip to content

Commit

Permalink
Tweaks, mostly to do with usability
Browse files Browse the repository at this point in the history
  • Loading branch information
macaba committed Aug 17, 2022
1 parent 9784e14 commit 59e650f
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 97 deletions.
37 changes: 18 additions & 19 deletions source/NSD/NSD.UI/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0">
<TabControl Grid.Row="0" Margin="0,0,0,6">
<TabItem Header="Process">
<Grid>
<Grid.RowDefinitions>
Expand Down Expand Up @@ -42,16 +42,16 @@

<Label Grid.Row="0" Grid.Column="0" VerticalAlignment="Center">Working folder</Label>
<TextBox Grid.Row="0" Grid.Column="1" Watermark="Working folder..." IsEnabled="{Binding Enabled}" Text="{Binding ProcessWorkingFolder}"/>
<Button Grid.Row="0" Grid.Column="2" Content="Search" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Click="BtnSearch_Click"></Button>
<Button Grid.Row="0" Grid.Column="2" Margin="6,0,0,0" Content="Search" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Click="BtnSearch_Click"></Button>

<Label Grid.Row="1" Grid.Column="0" VerticalAlignment="Center">Input CSV file</Label>
<ComboBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" Items="{Binding InputFileNames}" SelectedIndex="{Binding SelectedInputFileIndex}" IsEnabled="{Binding Enabled}"></ComboBox>

<Label Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">Sample rate (SPS)</Label>
<TextBox Grid.Row="3" Grid.Column="1" Watermark="Sample rate (SPS)..." Text="{Binding SampleRate}" IsEnabled="{Binding Enabled}"/>

<Label Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">Sample rate (SPS)</Label>
<TextBox Grid.Row="2" Grid.Column="1" Name="tbSampleRate" Watermark="Sample rate (SPS)..." Text="40" IsEnabled="{Binding Enabled}"/>

<Label Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">FFT width</Label>
<StackPanel Orientation="Horizontal" Grid.Row="3" Grid.Column="1">
<Label Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">FFT width</Label>
<StackPanel Orientation="Horizontal" Grid.Row="4" Grid.Column="1">
<ComboBox SelectedIndex="0" SelectedItem="{Binding SelectedFftWidthItem, Mode=OneWayToSource}" IsEnabled="{Binding Enabled}" HorizontalAlignment="Stretch">
<ComboBoxItem>64</ComboBoxItem>
<ComboBoxItem>128</ComboBoxItem>
Expand Down Expand Up @@ -83,21 +83,20 @@



<Button Grid.Row="4" Grid.ColumnSpan="3" Content="Run" HorizontalAlignment="Stretch" Click="btnRun_Click" IsEnabled="{Binding Enabled}"/>
<StackPanel Grid.Row="5" Grid.ColumnSpan="2" Orientation="Horizontal">
<Button Grid.Row="5" Grid.ColumnSpan="3" Margin="0,6,0,6" Content="Run" HorizontalAlignment="Stretch" Click="btnRun_Click" IsEnabled="{Binding Enabled}"/>
<StackPanel Grid.Row="6" Grid.ColumnSpan="2" Orientation="Horizontal">
<Label VerticalAlignment="Center">X Min</Label>
<NumericUpDown Value="{Binding XMin}" AllowSpin="false" ShowButtonSpinner="false" Width="100" IsEnabled="{Binding Enabled}"></NumericUpDown>
<Label VerticalAlignment="Center">X Max</Label>
<NumericUpDown Value="{Binding XMax}" AllowSpin="false" ShowButtonSpinner="false" Width="100" IsEnabled="{Binding Enabled}"></NumericUpDown>
</StackPanel>
<StackPanel Grid.Row="6" Grid.ColumnSpan="2" Orientation="Horizontal">
<Label VerticalAlignment="Center">Y Min</Label>
<NumericUpDown Value="{Binding YMin}" AllowSpin="false" ShowButtonSpinner="false" Width="100" IsEnabled="{Binding Enabled}"></NumericUpDown>
<Label VerticalAlignment="Center">Y Max</Label>
<NumericUpDown Value="{Binding YMax}" AllowSpin="false" ShowButtonSpinner="false" Width="100" IsEnabled="{Binding Enabled}"></NumericUpDown>
<CheckBox Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" Margin="6,0,0,0" Content="Savitzky-Golay filter" IsChecked="{Binding SgFilterChecked}" IsEnabled="{Binding Enabled}"></CheckBox>
</StackPanel>
<Button Grid.Row="7" Grid.ColumnSpan="3" Content="Set axis" HorizontalAlignment="Stretch" Click="btnSetAxis_Click" IsEnabled="{Binding Enabled}"></Button>
<CheckBox Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="2" x:Name="cbFilter" Content="Savitzky-Golay filter" IsChecked="false" IsEnabled="{Binding Enabled}"></CheckBox>
<Button Grid.Row="8" Grid.ColumnSpan="3" Margin="0,6,0,6" Content="Set axis" HorizontalAlignment="Stretch" Click="btnSetAxis_Click" IsEnabled="{Binding Enabled}"></Button>

</Grid>
<ScottPlot:AvaPlot Grid.Row="1" Name="WpfPlot1"/>
<Grid Grid.Row="2">
Expand All @@ -108,11 +107,11 @@
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" VerticalAlignment="Center">Output NSD file</Label>
<TextBox Grid.Row="0" Grid.Column="1" IsEnabled="{Binding Enabled}" Text="{Binding OutputFileName}"/>
<Button Grid.Row="0" Grid.Column="2" Content="Generate" IsEnabled="{Binding Enabled}" Click="BtnGenerate_Click"></Button>
<Button Grid.Row="0" Grid.Column="2" Margin="6,0,0,0" Content="Generate" IsEnabled="{Binding Enabled}" Click="BtnGenerate_Click"></Button>
</Grid>
</Grid>
</TabItem>
<TabItem Header="Collate">
<!--<TabItem Header="Collate">
<Grid>
<Grid>
<Grid.RowDefinitions>
Expand Down Expand Up @@ -164,10 +163,10 @@
<ScottPlot:AvaPlot Grid.Row="1" Name="WpfPlot2"/>
</Grid>
</Grid>
</TabItem>
</TabItem>-->
</TabControl>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="6">
<TextBlock Name="tbStatus" HorizontalAlignment="Stretch" Text="{Binding Status}"></TextBlock>
</StackPanel>
<Grid Grid.Row="1">
<TextBlock Name="tbStatus" Padding="6" HorizontalAlignment="Stretch" Text="{Binding Status}" Background="{Binding StatusBackground}"></TextBlock>
</Grid>
</Grid>
</Window>
152 changes: 83 additions & 69 deletions source/NSD/NSD.UI/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,84 +56,89 @@ public async void BtnCollateAdd_Click(object sender, RoutedEventArgs e)
{

}

public async void btnRun_Click(object sender, RoutedEventArgs e)
{
var path = viewModel.GetSelectedInputFilePath();
if (!File.Exists(path))
{
await ShowError("File not found", "Input CSV file not found");
viewModel.Enabled = true;
return;
}


if (!double.TryParse(tbSampleRate.Text, out double sampleRate))
{
await ShowError("Invalid sample rate", "Invalid sample rate value");
viewModel.Enabled = true;
return;
}
var fftWidth = int.Parse((string)(viewModel.SelectedFftWidthItem).Content);
var inputScaling = ((string)(viewModel.SelectedInputUnitItem).Content) switch
{
"V" => 1.0,
"mV" => 1e-3,
"uV" => 1e-6,
"nV" => 1e-9,
_ => 1.0
};

viewModel.Status = "Status: Loading CSV...";
viewModel.Enabled = false;

using var reader = new StreamReader(path);
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
var records = await csv.GetRecordsAsync<double>().ToListAsync();
if (records.Count == 0)
{
await ShowError("No CSV records", "No CSV records found");
viewModel.Enabled = true;
return;
}
if (fftWidth > records.Count)
{
await ShowError("FFT too long", "FFT width is longer than input data");
viewModel.Enabled = true;
return;
}

viewModel.Status = "Status: Calculating NSD...";

for (int i = 0; i < records.Count; i++)
try
{
records[i] *= inputScaling;
var path = viewModel.GetSelectedInputFilePath();
if (!File.Exists(path))
{
viewModel.Status = "Error: Input CSV file not found";
return;
}

if (!double.TryParse(viewModel.SampleRate, out double sampleRate))
{
viewModel.Status = "Error: Invalid sample rate value";
return;
}
var fftWidth = int.Parse((string)(viewModel.SelectedFftWidthItem).Content);
var inputScaling = ((string)(viewModel.SelectedInputUnitItem).Content) switch
{
"V" => 1.0,
"mV" => 1e-3,
"uV" => 1e-6,
"nV" => 1e-9,
_ => 1.0
};

viewModel.Status = "Status: Loading CSV...";

using var reader = new StreamReader(path);
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
var records = await csv.GetRecordsAsync<double>().ToListAsync();
if (records.Count == 0)
{
viewModel.Status = "Error: No CSV records found";
return;
}
if (fftWidth > records.Count)
{
viewModel.Status = "Error: FFT width is longer than input data";
return;
}

viewModel.Status = "Status: Calculating NSD...";

for (int i = 0; i < records.Count; i++)
{
records[i] *= inputScaling;
}
// Trim ignoreBins from either end of the real spectrum
int ignoreBins = 3; //FTNI = 3, HFT90 = 3
if (viewModel.FftStacking)
{
var nsd = await Welch.StackedNSD_Async(input: records.ToArray(), sampleRate, ignoreBins, outputWidth: fftWidth);
spectrum = nsd;
}
else
{
//var sine = Signals.OneVoltRmsTestSignal();
//await Welch.StackedNSD_Async(input: records.ToArray(), sampleRate, inputScale: 1e-3, outputWidth: fftWidth);
//var nsd = Welch.NSD_SingleSeries(input: sine, sampleRate, inputScale: 1, outputWidth: fftWidth);
var nsd = await Welch.NSD_Async(input: records.ToArray(), sampleRate, ignoreBins, outputWidth: fftWidth);
spectrum = nsd;
}

Memory<double> yArray;
if (viewModel.SgFilterChecked)
yArray = new SavitzkyGolayFilter(5, 1).Process(spectrum.Values.Span);
else
yArray = spectrum.Values;

UpdateNSDChart(spectrum.Frequencies, yArray);
viewModel.Status = "Status: Processing complete";
}
// Trim ignoreBins from either end of the real spectrum
int ignoreBins = 3; //FTNI = 3, HFT90 = 3
if (viewModel.FftStacking)
catch (Exception ex)
{
var nsd = await Welch.StackedNSD_Async(input: records.ToArray(), sampleRate, ignoreBins, outputWidth: fftWidth);
spectrum = nsd;
await ShowError("Exception", ex.Message);
}
else
finally
{
//var sine = Signals.OneVoltRmsTestSignal();
//await Welch.StackedNSD_Async(input: records.ToArray(), sampleRate, inputScale: 1e-3, outputWidth: fftWidth);
//var nsd = Welch.NSD_SingleSeries(input: sine, sampleRate, inputScale: 1, outputWidth: fftWidth);
var nsd = await Welch.NSD_Async(input: records.ToArray(), sampleRate, ignoreBins, outputWidth: fftWidth);
spectrum = nsd;
viewModel.Enabled = true;
}

Memory<double> yArray;
if (cbFilter.IsChecked == true)
yArray = new SavitzkyGolayFilter(5, 1).Process(spectrum.Values.Span);
else
yArray = spectrum.Values;

UpdateNSDChart(spectrum.Frequencies, yArray);
viewModel.Status = "Status: Processing complete";
viewModel.Enabled = true;
}

public async void BtnGenerate_Click(object sender, RoutedEventArgs e)
Expand All @@ -160,6 +165,15 @@ public async void BtnGenerate_Click(object sender, RoutedEventArgs e)

private void btnSetAxis_Click(object sender, RoutedEventArgs e)
{
if (spectrum != null)
{
Memory<double> yArray;
if (viewModel.SgFilterChecked)
yArray = new SavitzkyGolayFilter(5, 1).Process(spectrum.Values.Span);
else
yArray = spectrum.Values;
UpdateNSDChart(spectrum.Frequencies, yArray);
}
WpfPlot1.Plot.SetAxisLimits(Math.Log10(viewModel.XMin), Math.Log10(viewModel.XMax), Math.Log10(viewModel.YMin), Math.Log10(viewModel.YMax));
WpfPlot1.Render();
}
Expand Down
34 changes: 30 additions & 4 deletions source/NSD/NSD.UI/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.ObjectModel;
Expand All @@ -10,16 +11,22 @@ namespace NSD.UI
// https://docs.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/overview
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty] string status = "Status: Idle";
[ObservableProperty] bool enabled = true;
// Loaded from settings file
[ObservableProperty] string? processWorkingFolder;
[ObservableProperty] string? collateWorkingFolder;
[ObservableProperty] string? sampleRate;

[ObservableProperty] string status = "Status: Idle";
[ObservableProperty] bool enabled = true;
[ObservableProperty] ObservableCollection<string> inputFilePaths = new();
[ObservableProperty] ObservableCollection<string> inputFileNames = new();
[ObservableProperty] int selectedInputFileIndex = -1;
[ObservableProperty] string outputFileName = "output.nsd";
[ObservableProperty] bool sgFilterChecked = false;
[ObservableProperty] IBrush statusBackground = Brushes.WhiteSmoke;
public ComboBoxItem? SelectedFftWidthItem { get; set; }
public ComboBoxItem? SelectedInputUnitItem { get; set; }
public ComboBoxItem? SelectedFileFormatItem { get; set; }
public bool FftStacking { get; set; } = false;
public double XMin { get; set; } = 0.0001;
public double XMax { get; set; } = 10;
Expand All @@ -33,7 +40,8 @@ public MainWindowViewModel(Settings settings)
{
this.settings = settings;
processWorkingFolder = settings.ProcessWorkingFolder;
collateWorkingFolder = settings.CollateWorkingFolder;
//collateWorkingFolder = settings.CollateWorkingFolder;
sampleRate = settings.SampleRate;
}

partial void OnProcessWorkingFolderChanged(string? value)
Expand All @@ -44,10 +52,28 @@ partial void OnProcessWorkingFolderChanged(string? value)

partial void OnCollateWorkingFolderChanged(string? value)
{
settings.CollateWorkingFolder = value;
//settings.CollateWorkingFolder = value;
//settings.Save();
}

partial void OnSampleRateChanged(string? value)
{
settings.SampleRate = value;
settings.Save();
}

partial void OnStatusChanged(string value)
{
if(value.Contains("Error"))
{
StatusBackground = Brushes.Red;
}
else
{
StatusBackground = Brushes.WhiteSmoke;
}
}

public string GetSelectedInputFilePath()
{
if (inputFilePaths.Count > 0 && selectedInputFileIndex < inputFilePaths.Count)
Expand Down
6 changes: 4 additions & 2 deletions source/NSD/NSD.UI/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ namespace NSD.UI
public class Settings
{
public string? ProcessWorkingFolder { get; set; }
public string? CollateWorkingFolder { get; set; }
//public string? CollateWorkingFolder { get; set; }
public string? SampleRate { get; set; }

public static Settings Default()
{
return new Settings()
{
ProcessWorkingFolder = Directory.GetCurrentDirectory(),
CollateWorkingFolder = Directory.GetCurrentDirectory()
//CollateWorkingFolder = Directory.GetCurrentDirectory(),
SampleRate = "50"
};
}

Expand Down
9 changes: 6 additions & 3 deletions source/NSD/NSD/Welch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,22 @@ public static Spectrum StackedNSD(Memory<double> input, double sampleRate, int s
widths.Add(outputWidth);
startEndTrims.Add(startEndTrim);
int temp = outputWidth;
while (temp > 256)
while (temp > 64)
{
temp /= 2;
widths.Add(temp);
startEndTrims.Add(startEndTrim*2); //Trim a bit more for the shorter widths
if (temp == 64)
startEndTrims.Add(startEndTrim);
else
startEndTrims.Add(startEndTrim * 2); //Trim a bit more for the shorter widths
}
widths.Reverse(); // Smallest to largest
startEndTrims.Reverse();

double lowestFrequency = double.MaxValue;
List<double> outputFrequencies = new();
List<double> outputValues = new();
for(int n = 0; n < widths.Count; n++)
for (int n = 0; n < widths.Count; n++)
{
var nsd = NSD(input, sampleRate, startEndTrims[n], widths[n]);

Expand Down

0 comments on commit 59e650f

Please sign in to comment.