From ebf0bd11a9b40a2c5ee272daaf07b0d36fb46540 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 10:48:33 +0000 Subject: [PATCH 01/21] Fix issues with events and cropping --- .github/workflows/matlab.yml | 2 +- resources/ReleaseNotes.md | 24 +++---------------- .../IMAPTestingAnalysis.m | 8 +------ .../@IMAPTestingAnalysis/loadEventsData.m | 11 +++++++++ src/data/+mag/HK.m | 5 +++- src/data/+mag/Science.m | 4 +++- 6 files changed, 23 insertions(+), 31 deletions(-) diff --git a/.github/workflows/matlab.yml b/.github/workflows/matlab.yml index 624b4f33..f22cfa9d 100644 --- a/.github/workflows/matlab.yml +++ b/.github/workflows/matlab.yml @@ -38,7 +38,7 @@ jobs: if: github.ref == 'refs/heads/main' needs: test env: - VERSION: "2.5.0" + VERSION: "2.5.1" steps: - name: Check out repository uses: actions/checkout@v3 diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index 135748df..aef17e8d 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -1,23 +1,5 @@ -# App - -- Fix issue with default patterns on some computers - # Software -- Add separate classes for each specific HK type -- Add reference frame to science meta data -- Make `crop` a method of `mag.TimeSeries` -- Make magnitude and derivatives dependent properties of `mag.Science` -- Customize `get` method of `mag.Data` to accept multiple property names -- Allow charts to have `mag.Data` as input -- Rename `mag.AutomatedAnalysis` to `mag.IMAPTestingAnalysis` -- Rename `mag.Result` to `mag.PSD` -- Remove support for `Filters` in charts -- Remove implicit conversion methods for `table`, `timetable` and `tabular` -- Fix issues with setting colors in charts -- Add tests for `mag.Data`, `mag.Science` and `mag.HK` -- Add tests for `mag.graphics.chart.Area`, `mag.graphics.chart.Scatter`, `mag.graphics.chart.Stairs` and `mag.graphics.chart.Stem` plots - -# GitHub Workflows - -- Update dependencies to latest versions +- Replace last element of file with `missing` to improve plot where data is missing +- Fix issue with consecutive events of the same type missing a completion message +- Fix issue when cropping data and no timestamps are selected \ No newline at end of file diff --git a/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m b/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m index 9b7962fe..c631fe0a 100644 --- a/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m +++ b/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m @@ -400,16 +400,10 @@ function load(this) return; end - result.Primary.Data = result.Primary.Data(primaryPeriod, :); - result.Primary.Data.Properties.Events = result.Primary.Data.Properties.Events(primaryPeriod, :); - - result.Secondary.Data = result.Secondary.Data(secondaryPeriod, :); - result.Secondary.Data.Properties.Events = result.Secondary.Data.Properties.Events(secondaryPeriod, :); + result.crop(primaryPeriod, secondaryPeriod); if isempty(result.Primary.Data) || isempty(result.Secondary.Data) result = mag.Instrument.empty(); - else - result.cropDataBasedOnScience(); end end end diff --git a/src/analyze/+mag/@IMAPTestingAnalysis/loadEventsData.m b/src/analyze/+mag/@IMAPTestingAnalysis/loadEventsData.m index c168fb4a..4cf37fec 100644 --- a/src/analyze/+mag/@IMAPTestingAnalysis/loadEventsData.m +++ b/src/analyze/+mag/@IMAPTestingAnalysis/loadEventsData.m @@ -151,6 +151,16 @@ function loadEventsData(this) ae = acknowledgeEvents([acknowledgeEvents.timestamp] >= e.CommandTimestamp); ce = completedEvents([completedEvents.timestamp] >= e.CommandTimestamp); + % Remove responses to subsequent commands of the same type. + similarSubsequentEvents = events(([events.Type] == e.Type) & ([events.SubType] == e.SubType) & ([events.CommandTimestamp] > e.CommandTimestamp)); + + if ~isempty(similarSubsequentEvents) + + ae = ae([ae.timestamp] < similarSubsequentEvents(1).CommandTimestamp); + ce = ce([ce.timestamp] < similarSubsequentEvents(1).CommandTimestamp); + end + + % Find acknoledgement time. if isfield(ae, "type") && isfield(ae, "subtype") ae = ae((str2double([ae.type]) == e.Type) & (str2double([ae.subtype]) == e.SubType)); @@ -164,6 +174,7 @@ function loadEventsData(this) end end + % Find completion time. if isfield(ce, "type") && isfield(ce, "subtype") ce = ce((str2double([ce.type]) == e.Type) & (str2double([ce.subtype]) == e.SubType)); diff --git a/src/data/+mag/HK.m b/src/data/+mag/HK.m index e753faf0..1303b9da 100644 --- a/src/data/+mag/HK.m +++ b/src/data/+mag/HK.m @@ -51,7 +51,10 @@ function crop(this, timeFilter) for i = 1:numel(this) this(i).Data = this(i).Data(timeFilter, :); - this(i).MetaData.Timestamp = this(i).Time(1); + + if ~isempty(this(i).Time) + this(i).MetaData.Timestamp = this(i).Time(1); + end end end diff --git a/src/data/+mag/Science.m b/src/data/+mag/Science.m index a924ee9f..86257966 100644 --- a/src/data/+mag/Science.m +++ b/src/data/+mag/Science.m @@ -102,7 +102,9 @@ function crop(this, timeFilter) this.Data.Properties.Events = this.Data.Properties.Events(timePeriod, :); end - this.MetaData.Timestamp = this.Time(1); + if ~isempty(this.Time) + this.MetaData.Timestamp = this.Time(1); + end end function resample(this, targetFrequency) From c6d80e0222527b20d0a7820e90eb6103ccde7702 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 10:48:38 +0000 Subject: [PATCH 02/21] Replace last element of file with `missing` to improve plot where data is missing --- src/analyze/+mag/@IMAPTestingAnalysis/loadScienceData.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/analyze/+mag/@IMAPTestingAnalysis/loadScienceData.m b/src/analyze/+mag/@IMAPTestingAnalysis/loadScienceData.m index e6134a6a..ff54c8d2 100644 --- a/src/analyze/+mag/@IMAPTestingAnalysis/loadScienceData.m +++ b/src/analyze/+mag/@IMAPTestingAnalysis/loadScienceData.m @@ -50,6 +50,11 @@ function loadScienceData(this, primaryMetaData, secondaryMetaData) secondary = ps.apply(secondary, smd); end + % Remove last data point, to avoid continuous lines when data is + % missing. + primary{end, ["x", "y", "z"]} = missing(); + secondary{end, ["x", "y", "z"]} = missing(); + %% Convert to timetable primaryData = vertcat(primaryData, table2timetable(primary, RowTimes = "t")); %#ok From abdffbb1b91914637ded1d938d615a343f4aadd6 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 11:01:22 +0000 Subject: [PATCH 03/21] Update tests for science and HK for cropping --- src/data/+mag/HK.m | 4 +++- src/data/+mag/Science.m | 4 +++- tests/unit/data/tHK.m | 19 +++++++++++++++++++ tests/unit/data/tScience.m | 18 +++++++++++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/data/+mag/HK.m b/src/data/+mag/HK.m index 1303b9da..05354ae1 100644 --- a/src/data/+mag/HK.m +++ b/src/data/+mag/HK.m @@ -52,7 +52,9 @@ function crop(this, timeFilter) this(i).Data = this(i).Data(timeFilter, :); - if ~isempty(this(i).Time) + if isempty(this(i).Time) + this(i).MetaData.Timestamp = NaT(TimeZone = mag.process.DateTime.TimeZone); + else this(i).MetaData.Timestamp = this(i).Time(1); end end diff --git a/src/data/+mag/Science.m b/src/data/+mag/Science.m index 86257966..8efe3fa5 100644 --- a/src/data/+mag/Science.m +++ b/src/data/+mag/Science.m @@ -102,7 +102,9 @@ function crop(this, timeFilter) this.Data.Properties.Events = this.Data.Properties.Events(timePeriod, :); end - if ~isempty(this.Time) + if isempty(this.Time) + this.MetaData.Timestamp = NaT(TimeZone = mag.process.DateTime.TimeZone); + else this.MetaData.Timestamp = this.Time(1); end end diff --git a/tests/unit/data/tHK.m b/tests/unit/data/tHK.m index 60ec76a6..cff33164 100644 --- a/tests/unit/data/tHK.m +++ b/tests/unit/data/tHK.m @@ -18,6 +18,25 @@ function cropMethod_timerange(testCase) testCase.cropAndVerify(data, timeFilter, expectedTimes, expectedData); end + % Test that "crop" method does not fail when no data is selected. + function cropMethod_noSelection(testCase) + + % Set up. + data = testCase.createTestData(); + + % Exercise. + data.crop(timerange(datetime("Inf", TimeZone = "local"), datetime("-Inf", TimeZone = "local"))); + + % Verify. + for i = 1:numel(data) + + testCase.verifyEmpty(data(i).IndependentVariable, "All data should be cropped out."); + testCase.verifyEmpty(data(i).DependentVariables, "All data should be cropped out."); + + testCase.verifyTrue(ismissing(data(i).MetaData.Timestamp), "All data should be cropped out."); + end + end + % Test that "resample" method can resample to a higher frequency. function resampleMethod_higherFrequency(testCase) diff --git a/tests/unit/data/tScience.m b/tests/unit/data/tScience.m index 1fb0388f..26ec90ef 100644 --- a/tests/unit/data/tScience.m +++ b/tests/unit/data/tScience.m @@ -63,6 +63,22 @@ function cropMethod_events(testCase) testCase.verifyEqual(data.Events.Time, data.Time, "Data should be cropped as expected."); end + % Test that "crop" method does not fail when no data is selected. + function cropMethod_noSelection(testCase) + + % Set up. + data = testCase.createTestData(); + + % Exercise. + data.crop(timerange(datetime("Inf", TimeZone = "local"), datetime("-Inf", TimeZone = "local"))); + + % Verify. + testCase.verifyEmpty(data.IndependentVariable, "All data should be cropped out."); + testCase.verifyEmpty(data.DependentVariables, "All data should be cropped out."); + + testCase.verifyTrue(ismissing(data.MetaData.Timestamp), "All data should be cropped out."); + end + % Test that "resample" method can resample to a lower frequency. function resampleMethod_lowerFrequency(testCase) @@ -189,7 +205,7 @@ function cropAndVerify(testCase, data, timeFilter, expectedTimes, expectedData) function [data, rawData] = createTestData() rawData = timetable(datetime("now", TimeZone = "UTC") + minutes(1:10)', (1:10)', (11:20)', (21:30)', 3 * ones(10, 1), (1:10)', VariableNames = ["x", "y", "z", "range", "sequence"]); - data = mag.Science(rawData, mag.meta.Science()); + data = mag.Science(rawData, mag.meta.Science(Timestamp = datetime("now", TimeZone = "UTC"))); end end end From fa0753ef0f771ecb0f8c655198d3c87249d7a497 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 16:20:29 +0000 Subject: [PATCH 04/21] Fix app issues --- app/DataVisualization.mlapp | Bin 109668 -> 109414 bytes resources/ReleaseNotes.md | 5 +++++ 2 files changed, 5 insertions(+) diff --git a/app/DataVisualization.mlapp b/app/DataVisualization.mlapp index 454109cfa7837de449af1d04f9949b25b9fe70d6..fe0a8651f59f36de930250cc00fe66a54a2534f7 100644 GIT binary patch delta 89138 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 delta 89350 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 Date: Thu, 1 Feb 2024 16:21:14 +0000 Subject: [PATCH 05/21] Science object with no data is considered empty in `mag.Instrument` --- resources/ReleaseNotes.md | 1 + src/data/+mag/Instrument.m | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index 6f781715..f9f6e3b8 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -6,5 +6,6 @@ # Software - Replace last element of file with `missing` to improve plot where data is missing +- Science object with no data is considered empty in `mag.Instrument` - Fix issue with consecutive events of the same type missing a completion message - Fix issue when cropping data and no timestamps are selected \ No newline at end of file diff --git a/src/data/+mag/Instrument.m b/src/data/+mag/Instrument.m index 3c7d5fbd..eb6e147e 100644 --- a/src/data/+mag/Instrument.m +++ b/src/data/+mag/Instrument.m @@ -47,7 +47,9 @@ end function value = get.HasScience(this) - value = ~isempty(this.Primary) && ~isempty(this.Secondary); + + value = ~isempty(this.Primary) && ~isempty(this.Secondary) && ... + ~isempty(this.Primary.Data) && ~isempty(this.Secondary.Data); end function value = get.HasHK(this) From 4e83f450aa219665357b3a4c3f75a80e66060045 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 16:21:52 +0000 Subject: [PATCH 06/21] Reintroduce filtering for charts --- resources/ReleaseNotes.md | 1 + src/visualize/+mag/+graphics/+chart/Chart.m | 26 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index f9f6e3b8..6f59dab1 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -7,5 +7,6 @@ - Replace last element of file with `missing` to improve plot where data is missing - Science object with no data is considered empty in `mag.Instrument` +- Reintroduce filtering for charts - Fix issue with consecutive events of the same type missing a completion message - Fix issue when cropping data and no timestamps are selected \ No newline at end of file diff --git a/src/visualize/+mag/+graphics/+chart/Chart.m b/src/visualize/+mag/+graphics/+chart/Chart.m index fb639286..f3f65808 100644 --- a/src/visualize/+mag/+graphics/+chart/Chart.m +++ b/src/visualize/+mag/+graphics/+chart/Chart.m @@ -6,6 +6,8 @@ XVariable string {mustBeScalarOrEmpty} % YVARIABLES Name of variables plotted on y-axis. YVariables (1, :) string + % FILTER Filter x- and y-axis variables. + Filter (:, 1) logical = logical.empty() end methods (Abstract) @@ -35,6 +37,8 @@ else xData = data.(this.XVariable); end + + xData = this.applyFilter(xData); end function yData = getYData(this, data) @@ -61,6 +65,28 @@ yData = data.get(this.YVariables); end end + + yData = this.applyFilter(yData); + end + end + + methods (Access = private) + + function filteredData = applyFilter(this, data) + % APPLYFILTER Filter data based on specified filter. + + % No filtering. + if isempty(this.Filter) + filteredData = data; + + % Same filtering for each y-axis variable. + elseif height(this.Filter) == height(data) + filteredData = data(this.Filter, :); + + % Otherwise, throw an error. + else + error("Mismatch between filter and x- or y-axis variables."); + end end end end From e9d7ecb36593051b873ec002d04cd9b761682ee8 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 16:22:46 +0000 Subject: [PATCH 07/21] Fix issue with stem chart not showing marker --- resources/ReleaseNotes.md | 3 ++- src/visualize/+mag/+graphics/+chart/Stem.m | 1 + src/visualize/+mag/+graphics/+mixin/MarkerSupport.m | 10 +++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index 6f59dab1..f88eeead 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -9,4 +9,5 @@ - Science object with no data is considered empty in `mag.Instrument` - Reintroduce filtering for charts - Fix issue with consecutive events of the same type missing a completion message -- Fix issue when cropping data and no timestamps are selected \ No newline at end of file +- Fix issue when cropping data and no timestamps are selected +- Fix issue with `mag.graphics.chart.Stem` not showing markers \ No newline at end of file diff --git a/src/visualize/+mag/+graphics/+chart/Stem.m b/src/visualize/+mag/+graphics/+chart/Stem.m index a3d3c4f6..9f0c13c6 100644 --- a/src/visualize/+mag/+graphics/+chart/Stem.m +++ b/src/visualize/+mag/+graphics/+chart/Stem.m @@ -14,6 +14,7 @@ options.?mag.graphics.chart.Stem options.Marker (1, 1) string = "o" options.MarkerSize (1, 1) double = 6 + options.MarkerColor {mag.graphics.mixin.mustBeColor} = [] end this.assignProperties(options); diff --git a/src/visualize/+mag/+graphics/+mixin/MarkerSupport.m b/src/visualize/+mag/+graphics/+mixin/MarkerSupport.m index dee9966e..949cefd0 100644 --- a/src/visualize/+mag/+graphics/+mixin/MarkerSupport.m +++ b/src/visualize/+mag/+graphics/+mixin/MarkerSupport.m @@ -20,9 +20,13 @@ function style = get.MarkerStyle(this) style = {"Marker", this.Marker, ... - "MarkerSize", this.MarkerSize, ... - "MarkerEdgeColor", this.MarkerColor, ... - "MarkerFaceColor", this.MarkerColor}; + "MarkerSize", this.MarkerSize}; + + if ~isempty(this.MarkerColor) + + style = [style, {"MarkerEdgeColor", this.MarkerColor, ... + "MarkerFaceColor", this.MarkerColor}]; + end end end From 61027c03bb3c5c333053c52aa85838d2a69fb579 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 16:23:50 +0000 Subject: [PATCH 08/21] Add tests for `mag.graphics.mixin.mustBeColor` and fix typo in existing marker support test --- resources/ReleaseNotes.md | 4 +++- .../+mag/+graphics/+mixin/mustBeColor.m | 2 ++ tests/unit/visualize/MarkerSupportTestCase.m | 3 ++- tests/unit/visualize/tMustBeColor.m | 23 +++++++++++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/unit/visualize/tMustBeColor.m diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index f88eeead..1e8c5559 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -10,4 +10,6 @@ - Reintroduce filtering for charts - Fix issue with consecutive events of the same type missing a completion message - Fix issue when cropping data and no timestamps are selected -- Fix issue with `mag.graphics.chart.Stem` not showing markers \ No newline at end of file +- Fix issue with `mag.graphics.chart.Stem` not showing markers +- Add tests for `mag.graphics.mixin.mustBeColor` +- Fix typo in `MarkerSupportTestCase` \ No newline at end of file diff --git a/src/visualize/+mag/+graphics/+mixin/mustBeColor.m b/src/visualize/+mag/+graphics/+mixin/mustBeColor.m index ac0636c5..9d448d87 100644 --- a/src/visualize/+mag/+graphics/+mixin/mustBeColor.m +++ b/src/visualize/+mag/+graphics/+mixin/mustBeColor.m @@ -8,6 +8,8 @@ function mustBeColor(color) exception = MException("", "Invalid format for ""Colors"" property."); exception.throwAsCaller(); else + mustBeTextScalar(color); + mustBeNonzeroLengthText(color); end end \ No newline at end of file diff --git a/tests/unit/visualize/MarkerSupportTestCase.m b/tests/unit/visualize/MarkerSupportTestCase.m index 3b821d7e..a1a0cacb 100644 --- a/tests/unit/visualize/MarkerSupportTestCase.m +++ b/tests/unit/visualize/MarkerSupportTestCase.m @@ -6,7 +6,8 @@ struct(Name = "Marker", Value = 'o'), ... struct(Name = "MarkerSize", Value = 10), ... struct(Name = "MarkerColor", Value = "g", VerifiableName = "MarkerFaceColor", VerifiableValue = [0, 1, 0]), ... - struct(Name = "MarkerColor", Value = "g", VerifiableName = "MarkerEdgeColor", VerifiableValue = [0, 1, 0])} + struct(Name = "MarkerColor", Value = "#FF0000", VerifiableName = "MarkerFaceColor", VerifiableValue = [1, 0, 0]), ... + struct(Name = "MarkerColor", Value = [], VerifiableName = "MarkerEdgeColor", VerifiableValue = 'auto')} end methods (Test) diff --git a/tests/unit/visualize/tMustBeColor.m b/tests/unit/visualize/tMustBeColor.m new file mode 100644 index 00000000..87a01f00 --- /dev/null +++ b/tests/unit/visualize/tMustBeColor.m @@ -0,0 +1,23 @@ +classdef tMustBeColor < matlab.unittest.TestCase +% TMUSTBECOLOR Unit tests for "mag.graphics.chart.Area" class. + + properties (TestParameter) + SupportedColor = {[], [1, 0, 0], [0, 0.5, 0.25; 0.1, 0.3, 0.2], '', "green", 'red', "none", "#7be7d5"} + InvalidColor = {1, [1, 0], [1, 0, 0, 1], ""} + end + + methods (Test) + + % Test that supported values can be set as colors. + function supportedValues(~, SupportedColor) + mag.graphics.mixin.mustBeColor(SupportedColor); + end + + % Test that invalid values throw an error. + function invalidValues(testCase, InvalidColor) + + testCase.verifyError(@() mag.graphics.mixin.mustBeColor(InvalidColor), ?MException, ... + "Error should be thrown when unsupported value is used."); + end + end +end From 37673d55b9857c407d9adf46105fa4a944a082f8 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 16:31:59 +0000 Subject: [PATCH 09/21] Rename `cropDataBasedOnScience` to `cropToMatch` --- resources/ReleaseNotes.md | 1 + .../IMAPTestingAnalysis.m | 2 +- src/data/+mag/Instrument.m | 20 +++++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index 1e8c5559..9a1548fb 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -8,6 +8,7 @@ - Replace last element of file with `missing` to improve plot where data is missing - Science object with no data is considered empty in `mag.Instrument` - Reintroduce filtering for charts +- Rename `cropDataBasedOnScience` to `cropToMatch` - Fix issue with consecutive events of the same type missing a completion message - Fix issue when cropping data and no timestamps are selected - Fix issue with `mag.graphics.chart.Stem` not showing markers diff --git a/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m b/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m index c631fe0a..aae83afd 100644 --- a/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m +++ b/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m @@ -288,7 +288,7 @@ function load(this) rampMode.Secondary = this.SecondaryRamp; if rampMode.HasScience - rampMode.cropDataBasedOnScience(); + rampMode.cropToMatch(); else rampMode = mag.Instrument.empty(); end diff --git a/src/data/+mag/Instrument.m b/src/data/+mag/Instrument.m index eb6e147e..0637d5e0 100644 --- a/src/data/+mag/Instrument.m +++ b/src/data/+mag/Instrument.m @@ -103,7 +103,7 @@ function crop(this, primaryFilter, secondaryFilter) end this.cropScience(primaryFilter, secondaryFilter); - this.cropDataBasedOnScience(); + this.cropToMatch(); end function cropScience(this, primaryFilter, secondaryFilter) @@ -120,22 +120,26 @@ function cropScience(this, primaryFilter, secondaryFilter) this.Secondary.crop(secondaryFilter); end - function cropDataBasedOnScience(this) - % CROPDATABASEDONSCIENCE Crop meta data, events and HK based on - % science timestamps. + function cropToMatch(this, startTime, endTime) + % CROPTOMATCH Crop meta data, events and HK based on science + % timestamps or specified timestamps. - timeRange = this.TimeRange; + arguments + this (1, 1) mag.Instrument + startTime (1, 1) datetime = this.TimeRange(1) + endTime (1, 1) datetile = this.TimeRange(2) + end % Filter events. if ~isempty(this.Events) - this.Events = this.Events(isbetween([this.Events.CommandTimestamp], timeRange(1), timeRange(2), "closed")); + this.Events = this.Events(isbetween([this.Events.CommandTimestamp], startTime, endTime, "closed")); end % Filter HK. - this.HK.crop(timerange(timeRange(1), timeRange(2), "closed")); + this.HK.crop(timerange(startTime, endTime, "closed")); % Adjust meta data. - this.MetaData.Timestamp = timeRange(1); + this.MetaData.Timestamp = startTime; end function resample(this, targetFrequency) From 8a4f1e1d7b49fa8397783007e16ac2eb49f38a36 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 16:41:48 +0000 Subject: [PATCH 10/21] Add methods to replace data in science and in instrument during warm-up --- resources/ReleaseNotes.md | 2 ++ src/data/+mag/Instrument.m | 14 +++++++++++ src/data/+mag/Science.m | 32 +++++++++++++++++++++---- src/visualize/+mag/+graphics/sftPlots.m | 2 +- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index 9a1548fb..d390d21a 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -5,6 +5,8 @@ # Software +- Add method to `mag.Instrument` to fill warm-up with `missing` data +- Add method to `mag.Science` to replace periods with a filler variable - Replace last element of file with `missing` to improve plot where data is missing - Science object with no data is considered empty in `mag.Instrument` - Reintroduce filtering for charts diff --git a/src/data/+mag/Instrument.m b/src/data/+mag/Instrument.m index 0637d5e0..17285f50 100644 --- a/src/data/+mag/Instrument.m +++ b/src/data/+mag/Instrument.m @@ -92,6 +92,20 @@ sensor = supportedSensors(locSelected); end + function fillWarmUp(this, timePeriod, filler) + % FILLWARMUP Replace beginning of science mode with filler + % variable. + + arguments + this (1, 1) mag.Instrument + timePeriod (1, 1) duration = minutes(1) + filler (1, 1) double = missing() + end + + this.Primary.replace(timePeriod, filler); + this.Secondary.replace(timePeriod, filler); + end + function crop(this, primaryFilter, secondaryFilter) % CROP Crop data based on selected filters for primary and % secondary science. diff --git a/src/data/+mag/Science.m b/src/data/+mag/Science.m index 8efe3fa5..6aa1f2dc 100644 --- a/src/data/+mag/Science.m +++ b/src/data/+mag/Science.m @@ -90,11 +90,7 @@ function crop(this, timeFilter) timeFilter (1, 1) {mustBeA(timeFilter, ["duration", "timerange", "withtol"])} end - if isa(timeFilter, "duration") - timePeriod = timerange(this.Time(1) + timeFilter, this.Time(end), "closed"); - elseif isa(timeFilter, "timerange") || isa(timeFilter, "withtol") - timePeriod = timeFilter; - end + timePeriod = this.convertToTimePeriod(timeFilter); this.Data = this.Data(timePeriod, :); @@ -198,6 +194,20 @@ function filter(this, numeratorOrFilter, denominator) this.Data{1:numCoefficients, ["x", "y", "z"]} = missing(); end + function replace(this, timeFilter, filler) + % REPLACE Replace length of data specified by time filter with + % filler variable. + + arguments + this (1, 1) mag.Science + timeFilter (1, 1) {mustBeA(timeFilter, ["duration", "timerange", "withtol"])} + filler (1, 1) double = missing() + end + + timePeriod = this.convertToTimePeriod(timeFilter); + this.Data{timePeriod, ["x", "y", "z"]} = filler; + end + function data = computePSD(this, options) % COMPUTEPSD Compute the power spectral density of the magnetic % field measurements. @@ -263,4 +273,16 @@ function filter(this, numeratorOrFilter, denominator) end end end + + methods (Static, Access = private) + + function timePeriod = convertToTimePeriod(timeFilter) + + if isa(timeFilter, "duration") + timePeriod = timerange(this.Time(1) + timeFilter, this.Time(end), "closed"); + elseif isa(timeFilter, "timerange") || isa(timeFilter, "withtol") + timePeriod = timeFilter; + end + end + end end diff --git a/src/visualize/+mag/+graphics/sftPlots.m b/src/visualize/+mag/+graphics/sftPlots.m index 6a2609cf..8d9d496e 100644 --- a/src/visualize/+mag/+graphics/sftPlots.m +++ b/src/visualize/+mag/+graphics/sftPlots.m @@ -19,7 +19,7 @@ if ~isempty(options.Filter) analysis = analysis.copy(); - analysis.Results.cropScience(options.Filter); + analysis.Results.fillWarmUp(options.Filter); end % Separate modes. From 893e33ebfed0ecf5ed0a670494b2957a7fb0819c Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 16:52:26 +0000 Subject: [PATCH 11/21] Add tests for `replace` method --- src/data/+mag/Science.m | 25 +++++++++++-------------- tests/unit/data/tScience.m | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/data/+mag/Science.m b/src/data/+mag/Science.m index 6aa1f2dc..2c94afec 100644 --- a/src/data/+mag/Science.m +++ b/src/data/+mag/Science.m @@ -90,7 +90,11 @@ function crop(this, timeFilter) timeFilter (1, 1) {mustBeA(timeFilter, ["duration", "timerange", "withtol"])} end - timePeriod = this.convertToTimePeriod(timeFilter); + if isa(timeFilter, "duration") + timePeriod = timerange(this.Time(1) + timeFilter, this.Time(end), "closed"); + elseif isa(timeFilter, "timerange") || isa(timeFilter, "withtol") + timePeriod = timeFilter; + end this.Data = this.Data(timePeriod, :); @@ -204,7 +208,12 @@ function replace(this, timeFilter, filler) filler (1, 1) double = missing() end - timePeriod = this.convertToTimePeriod(timeFilter); + if isa(timeFilter, "duration") + timePeriod = timerange(this.Time(1), this.Time(1) + timeFilter, "closed"); + elseif isa(timeFilter, "timerange") || isa(timeFilter, "withtol") + timePeriod = timeFilter; + end + this.Data{timePeriod, ["x", "y", "z"]} = filler; end @@ -273,16 +282,4 @@ function replace(this, timeFilter, filler) end end end - - methods (Static, Access = private) - - function timePeriod = convertToTimePeriod(timeFilter) - - if isa(timeFilter, "duration") - timePeriod = timerange(this.Time(1) + timeFilter, this.Time(end), "closed"); - elseif isa(timeFilter, "timerange") || isa(timeFilter, "withtol") - timePeriod = timeFilter; - end - end - end end diff --git a/tests/unit/data/tScience.m b/tests/unit/data/tScience.m index 26ec90ef..50853359 100644 --- a/tests/unit/data/tScience.m +++ b/tests/unit/data/tScience.m @@ -180,6 +180,44 @@ function filterMethod_digitalFilter(testCase) testCase.assertSize(downsampledData.DependentVariables, size(data.DependentVariables), "Filtering should not affect size."); testCase.verifyTrue(all(ismissing(downsampledData.XYZ), "all"), "All data should be replaced with missing values to account for filter warm-up."); end + + % Test that "replace" method replaces data with default filler. + function replaceMethod_default(testCase) + + % Set up. + data = testCase.createTestData(); + + % Exercise. + modifiedData = data.copy(); + modifiedData.replace(minutes(3)); + + % Verify. + testCase.assertSize(modifiedData.IndependentVariable, size(data.Time), "Time should not be modified."); + testCase.verifyEqual(modifiedData.Time, data.Time, "Time should not be modified."); + + testCase.assertSize(modifiedData.DependentVariables, size(data.DependentVariables), "Data size should not change."); + testCase.verifyTrue(all(ismissing(modifiedData.XYZ(1:4, :)), "all"), "Data within filter should be replaced."); + testCase.verifyEqual(modifiedData.DependentVariables(5:end, :), data.DependentVariables(5:end, :), "Only data within filter should be replaced."); + end + + % Test that "replace" method replaces data with default filler. + function replaceMethod_specified(testCase) + + % Set up. + data = testCase.createTestData(); + + % Exercise. + modifiedData = data.copy(); + modifiedData.replace(minutes(3), 0); + + % Verify. + testCase.assertSize(modifiedData.IndependentVariable, size(data.Time), "Time should not be modified."); + testCase.verifyEqual(modifiedData.Time, data.Time, "Time should not be modified."); + + testCase.assertSize(modifiedData.DependentVariables, size(data.DependentVariables), "Data size should not change."); + testCase.verifyEqual(modifiedData.XYZ(1:4, :), zeros(4, 3), "Data within filter should be replaced."); + testCase.verifyEqual(modifiedData.DependentVariables(5:end, :), data.DependentVariables(5:end, :), "Only data within filter should be replaced."); + end end methods (Access = private) From 52d73d559f58338860a7d37533605bf6b41b995c Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 17:19:56 +0000 Subject: [PATCH 12/21] Add tests to cover all dependent properties of HK types --- tests/data/Power.csv | 18 ++++++++++++++++++ tests/data/Processor.csv | 5 +++++ tests/data/SID15.csv | 4 ++++ tests/data/Status.csv | 7 +++++++ tests/unit/data/tHK.m | 25 +++++++++++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 tests/data/Power.csv create mode 100644 tests/data/Processor.csv create mode 100644 tests/data/SID15.csv create mode 100644 tests/data/Status.csv diff --git a/tests/data/Power.csv b/tests/data/Power.csv new file mode 100644 index 00000000..9658c008 --- /dev/null +++ b/tests/data/Power.csv @@ -0,0 +1,18 @@ +PHVERNO,PHTYPE,PHSHF,PHAPID,PHGROUPF,PHSEQCNT,PHDLEN,SHCOARSE,PUS_SPARE1,PUS_VERSION,PUS_SPARE2,PUS_STYPE,PUS_SSUBTYPE,HK_STRUCID,P1V5V,P1V8V,P3V3V,P2V5V,P8V,N8V,ICU_TEMP,P2V4V,P1V5I,P1V8I,P3V3I,P2V5I,P8VI,N8VI,FOB_TEMP,FIB_TEMP,MAGOSATFLAGX,MAGOSATFLAGY,MAGOSATFLAGZ,MAGISATFLAGX,MAGISATFLAGY,MAGISATFLAGZ,SPARE1,MAGORANGE,MAGIRANGE,SPARE2,MAGITFMISSCNT,timestamp +0,0,1,1063,3,0,43,444491465,0,1,0,3,25,3,1898,2278,2891,3174,3781,3755,2357,2030,1781,384,1650,1845,1084,838,2716,2702,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:51:17.065224 +0,0,1,1063,3,1,43,444491485,0,1,0,3,25,3,1897,2279,2891,3174,3781,3755,2359,2032,1784,356,1631,1842,1082,837,2716,2702,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:51:37.012682 +0,0,1,1063,3,0,43,444491492,0,1,0,3,25,3,1897,2279,2891,3174,3782,3755,2359,2028,1804,414,1663,1842,1082,838,2716,2702,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:51:44.124161 +0,0,1,1063,3,1,43,444491512,0,1,0,3,25,3,1897,2279,2891,3174,3781,3755,2360,2028,1816,357,1627,1841,1081,837,2716,2702,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:52:04.024169 +0,0,1,1063,3,2,43,444491532,0,1,0,3,25,3,1897,2278,2891,3174,3752,3727,2361,2028,1815,381,1634,1916,1383,1101,2716,2702,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:52:24.014244 +0,0,1,1063,3,3,43,444491552,0,1,0,3,25,3,1897,2278,2891,3174,3752,3727,2362,2027,1817,381,1634,1915,1383,1101,2715,2701,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:52:44.014430 +0,0,1,1063,3,4,43,444491564,0,1,0,3,25,3,1897,2278,2891,3174,3751,3726,2362,2027,1820,377,1632,1915,1382,1101,2715,2701,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:52:56.014604 +0,0,1,1063,3,5,43,444491572,0,1,0,3,25,3,1897,2279,2891,3174,3751,3726,2362,2026,1822,378,1632,1915,1382,1101,2715,2701,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:53:04.014261 +0,0,1,1063,3,6,43,444491592,0,1,0,3,25,3,1897,2279,2891,3174,3751,3726,2363,2027,1812,378,1612,1914,1382,1101,2716,2701,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:53:24.014310 +0,0,1,1063,3,7,43,444491612,0,1,0,3,25,3,1897,2278,2891,3174,3722,3698,2364,2025,1821,402,1638,1989,1701,1382,2715,2700,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:53:44.024637 +0,0,1,1063,3,8,43,444491632,0,1,0,3,25,3,1897,2279,2891,3174,3722,3698,2364,2025,1817,400,1636,1989,1700,1381,2715,2700,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:54:04.004364 +0,0,1,1063,3,9,43,444491635,0,1,0,3,25,3,1897,2278,2891,3174,3722,3698,2364,2025,1821,400,1635,1988,1700,1381,2715,2700,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:54:07.064015 +0,0,1,1063,3,10,43,444491652,0,1,0,3,25,3,1897,2278,2891,3174,3721,3697,2365,2024,1779,540,1792,1988,1700,1381,2715,2700,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:54:24.014391 +0,0,1,1063,3,11,43,444491672,0,1,0,3,25,3,1897,2278,2891,3174,3721,3697,2365,2025,1774,535,1792,1988,1700,1381,2716,2700,0,0,0,0,0,0,0,0,0,0,0,2024-02-01 13:54:44.085040 +0,0,1,1063,3,12,43,444491692,0,1,0,3,25,3,1897,2278,2891,3174,3720,3697,2366,2024,1777,538,1812,1988,1704,1385,2716,2700,0,0,0,0,0,0,0,1,0,0,0,2024-02-01 13:55:04.015085 +0,0,1,1063,3,13,43,444491700,0,1,0,3,25,3,1897,2278,2891,3174,3720,3697,2366,2024,1778,533,1805,1987,1704,1385,2716,2700,0,0,0,0,0,0,0,1,0,0,0,2024-02-01 13:55:12.013524 +0,0,1,1063,3,14,43,444491712,0,1,0,3,25,3,1897,2278,2891,3174,3720,3696,2367,2024,1782,535,1805,1987,1707,1389,2716,2700,0,0,0,0,0,0,0,2,1,0,0,2024-02-01 13:55:24.013303 \ No newline at end of file diff --git a/tests/data/Processor.csv b/tests/data/Processor.csv new file mode 100644 index 00000000..4d41f008 --- /dev/null +++ b/tests/data/Processor.csv @@ -0,0 +1,5 @@ +PHVERNO,PHTYPE,PHSHF,PHAPID,PHGROUPF,PHSEQCNT,PHDLEN,SHCOARSE,PUS_SPARE1,PUS_VERSION,PUS_SPARE2,PUS_STYPE,PUS_SSUBTYPE,HK_STRUCID,TOTAL_TASKS,TOTAL_TM_CPUUSG,TOTAL_TM_CPUUSG_US,IDLE_TSK_CPUUSG,IDLE_TSK_CPUUSG_US,SPIR_TSK_CPUUSG,SPIR_TSK_CPUUSG_US,IBBT_TSK_CPUUSG,IBBT_TSK_CPUUSG_US,IBNT_TSK_CPUUSG,IBNT_TSK_CPUUSG_US,OBBT_TSK_CPUUSG,OBBT_TSK_CPUUSG_US,OBNT_TSK_CPUUSG,OBNT_TSK_CPUUSG_US,SCIT_TSK_CPUUSG,SCIT_TSK_CPUUSG_US,TCIM_TSK_CPUUSG,TCIM_TSK_CPUUSG_US,TCIR_TSK_CPUUSG,TCIR_TSK_CPUUSG_US,TMOT_TSK_CPUUSG,TMOT_TSK_CPUUSG_US,SRMT_TSK_CPUUSG,SRMT_TSK_CPUUSG_US,MONT_TSK_CPUUSG,MONT_TSK_CPUUSG_US,MSCR_TSK_CPUUSG,MSCR_TSK_CPUUSG_US,WDOG_TSK_CPUUSG,WDOG_TSK_CPUUSG_US,BRST_TSK_CPUUSG,BRST_TSK_CPUUSG_US,SAPT_TSK_CPUUSG,SAPT_TSK_CPUUSG_US,DBGT_TSK_CPUUSG,DBGT_TSK_CPUUSG_US,FIB_DTRDY_ASSERTTM,FOB_DTRDY_ASSERTTM,TM_QSND_FAIL,TM_QFAILCODE,SRAM_SINGBITERRCNT,NUM_ACC_TC,NUM_EXEC_TC,NUM_REJ_TC,NUM_NONEXEC_TC,TMPQ_NUM_MSG,TCPQ_NUM_MSG,IBNQ_NUM_MSG,OBNQ_NUM_MSG,IBRB_NUM_MSG,OBRB_NUM_MSG,IBBB_NUM_MSG,OBBB_NUM_MSG,UP_TM,LST_FAIL_TC_OC,LST_FAIL_TC_TY,LST_FAIL_TC_ST,LST_SUC_TC_OC,LST_SUC_TC_TY,LST_SUC_TC_ST,ITF_MSD_FR,ITF_REJ_FR,timestamp +0,0,1,1051,3,0,199,444491465,0,1,0,3,25,11,7,1,612782,1,477551,0,0,0,0,0,0,0,0,0,0,0,0,0,13413,0,0,0,8759,0,693,0,4660,0,131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,255,255,0,0,0,0,1,0,0,0,0,0,0,0,0,2024-02-01 13:51:17.068224 +0,0,1,1051,3,0,199,444491492,0,1,0,3,25,11,15,1,646148,1,473529,0,147,0,149,0,204,0,115,0,131,0,133,0,13347,0,0,0,9161,0,167,0,5798,0,130,0,0,0,132,0,119,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,2024-02-01 13:51:44.126168 +0,0,1,1051,3,1,199,444495092,0,1,0,3,25,11,15,3600,94605,2089,354686,502,527637,235,354474,176,55674,244,533689,183,178487,2,83708,19,376469,0,0,61,78210,0,30893,7,18503,0,62396,0,0,79,317251,0,122528,0,0,1930985870,1931212374,0,0,0,20,20,0,0,1,0,6,0,0,32,143,2,3601,0,0,0,43,3,136,0,0,2024-02-01 14:51:44.069847 +0,0,1,1051,3,2,199,444498572,0,1,0,3,25,11,15,3480,12786,2103,665361,467,419023,210,461576,157,234053,218,824806,163,631292,1,826508,18,743812,0,0,57,788301,0,8411,6,790488,0,60290,0,0,73,558865,0,0,0,0,186016750,185963638,0,0,0,43,42,0,0,0,0,14,0,0,0,145,15,7081,0,0,0,82,226,6,0,0,2024-02-01 15:49:44.130665 diff --git a/tests/data/SID15.csv b/tests/data/SID15.csv new file mode 100644 index 00000000..331a0530 --- /dev/null +++ b/tests/data/SID15.csv @@ -0,0 +1,4 @@ +PHVERNO,PHTYPE,PHSHF,PHAPID,PHGROUPF,PHSEQCNT,PHDLEN,SHCOARSE,PUS_SPARE1,PUS_VERSION,PUS_SPARE2,PUS_STYPE,PUS_SSUBTYPE,HK_STRUCID,ISV_SRAMADDR,ICV_SRAMADDR,ISV_STRUCVER,ISV_MODE,ISV_ITF_MSD_FR,ISV_FIB_LINKSTAT,SPARE1,ISV_FIB_RSTSTAT,SPARE2,ISV_FIB_STATWRD,ISV_FIB_RNG,ISV_FIB_DTRDYTM,ISV_FIB_LSTNVECX,ISV_FIB_LSTNVECY,ISV_FIB_LSTNVECZ,ISV_FIB_LSTNVECTM,ISV_FIB_LSTNVECSTAT,ISV_FIB_LSTNVECRNG,ISV_FOB_LINKSTAT,SPARE4,ISV_FOB_RSTSTAT,SPARE5,FOB_STAT_WRD,ISV_FOB_RNG,ISV_FOB_DTRDYTM,ISV_FOB_LSTNVECX,ISV_FOB_LSTNVECY,ISV_FOB_LSTNVECZ,ISV_FOB_LSTNVECTM,SPARE7,ISV_FOB_LSTNVECRNG,ISV_LSTFAILCMD_OC,ISV_LSTFAILCMD_TYPE,ISV_LSTFAILCMD_STYPE,ISV_LSTSUCCCMD_OC,ISV_LSTSUCCCMD_TYPE,ISV_LSTSUCCCMD_STYPE,ISV_LSTITFERR_CS_TM,ISV_LSTITFERR_FN_TM,SPARE9,ISV_ITFREJ_FRM,SPARE10,ISV_AUTO_PWRREQ,ISV_SW_VERPTCH,ISV_SWVERSPARE,ISV_SW_VERREL,ISV_SW_VERNUM,ISV_NUM_ACC_TC,ISV_NUM_EXEC_TC,ISV_NUM_REJ_TC,ISV_NUM_NONEXEC_TC,ISV_CLKDRFT_FT,ISV_CLKDRFT_CT,ISV_DRFT_TMSTMP,ISV_TM_SYNCFLG,ISV_TMCD_INT,SPARE11,ISV_HKTMGLOBMSK,ISV_P1V5V,ISV_P1V8V,ISV_P3V3V,ISV_P2V5V,ISV_P8V,ISV_N8V,ISV_ICU_TEMP,ISV_P2V4V,ISV_P1V5I,ISV_P1V8I,ISV_P3V3I,ISV_P2V5I,ISV_P8VI,ISV_N8VI,ISV_FOB_TEMP,ISV_FIB_TEMP,ISV_FOB_ACTPROC,ISV_FIB_ACTPROC,SPARE12,ISV_MODECHNG_RSN,ISV_DATARATE,ISV_TRRIGTM,ISV_FOB_ACTTRIES,ISV_FIB_ACTTRIES,SPARE14,SPARE15,ISV_TLMQ_FAILCNT,ISV_LSTTLMQ_FAILSTAT,ISV_IDLECPU_FLG,ISV_TSKSTK_FLG1,ISV_TSKSTK_FLG2,ISV_TSKSTK_FLG3,ISV_TSKSTK_FLG4,ISV_TSKSTK_FLG5,ISV_TSKSTK_FLG6,ISV_TSKSTK_FLG7,ISV_TSKSTK_FLG8,ISV_TSKSTK_FLG9,ISV_TSKSTK_FLG10,ISV_TSKSTK_FLG11,ISV_TSKSTK_FLG12,ISV_TSKSTK_FLG13,ISV_TSKSTK_FLG14,ISV_TSKSTK_FLG15,ISV_TSKSTK_FLG16,ISV_TSKSTK_FLG17,ISV_TSKSTK_FLG18,ISV_WDOG_FLG1,ISV_WDOG_FLG2,ISV_WDOG_FLG3,ISV_WDOG_FLG4,ISV_WDOG_FLG5,ISV_WDOG_FLG6,ISV_WDOG_FLG7,ISV_WDOG_FLG8,ISV_WDOG_FLG9,ISV_WDOG_FLG10,ISV_WDOG_FLG11,ISV_WDOG_FLG12,ISV_WDOG_FLG13,ISV_WDOG_FLG14,ISV_WDOG_FLG15,ISV_WDOG_FLG16,ISV_WDOG_FLG17,ISV_WDTINIT,SPARE16,ISV_PROG_COUNTED,ISV_PROG_CNTTSDKID,ISV_SINGBITERRS,ISV_MLTBITERRS,ISV_EDAC_EN,ISV_SINGBITERRADD,ISV_MLTBITERRADD,ISV_TRIGCNTDWN,ISV_FOB_ACTWAITTM,ISV_FIB_ACTWAITTM,ISV_IIC_MATSRAMADDR,ISV_FIB_MSSDSPIBLK,ISV_FOB_MSSDSPIBLK,ISV_BTHSPIEVNTS,ISV_FIB_NORMQFAIL,ISV_FOB_NORMQFAIL,ISV_UNKNOWN_ERR,ISV_SPIR_E_RXFAIL,ISV_SPIR_E_RXUNEXP,ISV_SPIR_S_FAILOBS,ISV_SPIR_B_OWRTOBS,ISV_SPIR_S_FAILIBS,ISV_SPIR_B_OWRTIBS,ISV_SPIR_E_SNDFAILBOBS,ISV_SPIR_E_SNDFAILNOBS,ISV_SPIR_E_SNDFAILBIBS,ISV_SPIR_E_SNDFAILNIBS,ISV_IBBT_E_RXFAIL,ISV_IBBT_S_FAIL,ISV_IBBT_B_SNDFAIL,ISV_IBNT_E_RXFAIL,ISV_IBNT_S_FAIL,ISV_IBNT_Q_SNDFAIL,ISV_OBBT_E_RXFAIL,ISV_OBBT_S_FAIL,ISV_OBBT_B_SNDFAIL,ISV_OBNT_E_RXFAIL,ISV_OBNT_S_FAIL,ISV_OBNT_Q_SNDFAIL,ISV_SCIT_OBNQ_RXFAIL,ISV_SCIT_IBNQ_RXFAIL,ISV_SCIT_UNKNOWNM,ISV_TCIM_Q_SNDFAIL,ISV_TMOT_Q_RXFAIL,ISV_TMOT_PKTDRP,ISV_TMOT_SPWWRTFAILEV,ISV_SRMT_Q_RXFAIL,ISV_MONT_E_RXFAIL,ISV_MONT_SRVC3INVAL,ISV_MSCR_TSKSSPNDFAIL,ISV_BRST_TSKSSPNDFAIL,ISV_BRST_BRXFAILOBS,ISV_BRST_BRXFAILIBS,ISV_BRST_UNKNOWN_M,ISV_SAPT_ERXFAIL,ISV_SPI_FIB_ESNDFAIL,ISV_SPI_FOB_ESNDFAIL,ISV_TMCODE_TFIREFAIL,ISV_MONT_ESNDFAIL,ISV_BSP_ASW_UNEXPTRAP,ISV_EDACSRAM_MULTBITERR,ISV_DEBUG_QRX_FAIL,SPARE17,ISV_FIB_RAWX,ISV_FIB_RAWY,ISV_FIB_RAWZ,ISV_FOB_RAWX,ISV_FOB_RAWY,ISV_FOB_RAWZ,ISV_ITFTC_SSC,ISV_SPIMISSEDRD_VOLT,ISV_SPIMISSEDRD_CUR,ISV_GITBUILD_ID,ICV_STRUCVER,ICV_VER,ICV_PRISENSOR,ICV_COMPRS_FLG,SPARE18,ICV_PRI_SPW,ICV_FEEWT_REACT,ICV_GRTCM_INITRETRIES,ICV_ERRREPLIM,ICV_FIB_RG0GAINX,ICV_FIB_RG0GAINY,ICV_FIB_RG0GAINZ,ICV_FIB_RG1GAINX,ICV_FIB_RG1GAINY,ICV_FIB_RG1GAINZ,ICV_FIB_RG2GAINX,ICV_FIB_RG2GAINY,ICV_FIB_RG2GAINZ,ICV_FIB_RG3GAINX,ICV_FIB_RG3GAINY,ICV_FIB_RG3GAINZ,ICV_FOB_RG0GAINX,ICV_FOB_RG0GAINY,ICV_FOB_RG0GAINZ,ICV_FOB_RG1GAINX,ICV_FOB_RG1GAINY,ICV_FOB_RG1GAINZ,ICV_FOB_RG2GAINX,ICV_FOB_RG2GAINY,ICV_FOB_RG2GAINZ,ICV_FOB_RG3GAINX,ICV_FOB_RG3GAINY,ICV_FOB_RG3GAINZ,ICV_TM_TICKS1,ICV_TM_TICKS2,ICV_HK1_EN,ICV_HK1_ISVALID,SPARE21,ICV_TMR_CNT,ICV_HK1_ADDR,ICV_HK2_EN,ICV_HK2_ISVALID,SPARE22,ICV_HK2_TMRCNT,ICV_HK2_ADDR,ICV_HK3_EN,ICV_HK3_ISVALID,SPARE23,ICV_HK3_TMRCNT,ICV_HK3_ADDR,ICV_HK4_EN,ICV_HK4_ISVALID,SPARE24,ICV_HK4_TMRCNT,ICV_HK4_ADDR,ICV_HK5_EN,ICV_HK5_ISVALID,SPARE25,ICV_HK5_TMRCNT,ICV_HK5_ADDR,ICV_HK6_EN,ICV_HK6_ISVALID,SPARE26,ICV_HK6_TMRCNT,ICV_HK6_ADDR,ICV_HK7_EN,ICV_HK7_ISVALID,SPARE27,ICV_HK7_TMRCNT,ICV_HK7_ADDR,ICV_HK8_EN,ICV_HK8_ISVALID,SPARE28,ICV_HK8_TMRCNT,ICV_HK8_ADDR,ICV_HK9_EN,ICV_HK9_ISVALID,SPARE29,ICV_HK9_TMRCNT,ICV_HK9_ADDR,ICV_HK10_EN,ICV_HK10_ISVALID,SPARE30,ICV_HK10_TMRCNT,ICV_HK10_ADDR,ICV_HK11_EN,ICV_HK11_ISVALID,SPARE31,ICV_HK11_TMRCNT,ICV_HK11_ADDR,ICV_HK12_EN,ICV_HK13_EN,ICV_HK14_EN,ICV_HK15_EN,ICV_HK16_EN,ICV_HK20_EN,ICV_LL_RATE,ICV_1920_EN,ICV_VECBITSHFT,ICV_FILTTYPE_IBSB,ICV_SPARE1_IBSB,ICV_OUTRATE_IBSB,ICV_DECIMATION_IBSB,ICV_SHIFT_IBSB,ICV_PKTSECS_IBSB,ICV_OUTBUFFADDR_IBSB,ICV_INPBUFFADDR_IBSB,ICV_OUTQID_IBSB,ICV_FILTTYPE_IBSN,ICV_SPARE1_IBSN,ICV_OUTRATE_IBSN,ICV_DECIMATION_IBSN,ICV_SHIFT_IBSN,ICV_PKTSECS_IBSN,ICV_OUTBUFFADDR_IBSN,ICV_INPBUFFADDR_IBSN,ICV_OUTQID_IBSN,ICV_FILTTYPE_OBSB,ICV_SPARE1_OBSB,ICV_OUTRATE_OBSB,ICV_DECIMATION_OBSB,ICV_SHIFT_OBSB,ICV_PKTSECS_OBSB,ICV_OUTBUFFADDR_OBSB,ICV_INPBUFFADDR_OBSB,ICV_OUTQID_OBSB,ICV_FILTTYPE_OBSN,ICV_SPARE1_OBSN,ICV_OUTRATE_OBSN,ICV_DECIMATION_OBSN,ICV_SHIFT_OBSN,ICV_PKTSECS_OBSN,ICV_OUTBUFFADDR_OBSN,ICV_INPBUFFADDR_OBSN,ICV_OUTQID_OBSN,ICV_BRSTBUFF_SECS,ICV_AUTO2SAFE,ICV_WDT_EN,ICV_FEETC_TRIESBFREP,ICV_GLBBRSTTRIG,ICV_SRAMSCRBRT,ICV_SRAMSCRBLEN,ICV_SRAMSCRB_EN,ICV_MONINT_CNT,ICV_NUMTM_RETRIES,ICV_BUFF_SZ1,ICV_BUFF_SZ2,ICV_BUFF_SZ3,ICV_BUFF_SZ4,ICV_Q_SZ1,ICV_Q_SZ2,ICV_Q_SZ3,ICV_Q_SZ4,ICV_Q_SZ5,ICV_TSK_PRIOR1,ICV_TSK_PRIOR2,ICV_TSK_PRIOR3,ICV_TSK_PRIOR4,ICV_TSK_PRIOR5,ICV_TSK_PRIOR6,ICV_TSK_PRIOR7,ICV_TSK_PRIOR8,ICV_TSK_PRIOR9,ICV_TSK_PRIOR10,ICV_TSK_PRIOR11,ICV_TSK_PRIOR12,ICV_TSK_PRIOR13,ICV_TSK_PRIOR14,ICV_TSK_PRIOR15,ICV_TSK_PRIOR16,ICV_TSK_PRIOR17,SPARE38,SPARE39,SPARE40,ICV_IDLECPU_LIM,ICV_CPUMEMMON_ACT,ICV_P1V5V_DANGERUP,ICV_P1V5V_DANGERLOW,ICV_P1V5V_WARNUP,ICV_P1V5V_WARNLOW,ICV_ISACT_1V5V,ICV_AUTOACTION_1V5V,SPARE41,SPARE42,ICV_P1V8V_DANGERUP,ICV_P1V8V_DANGERLOW,ICV_P1V8V_WARNUP,ICV_P1V8V_WARNLOW,ICV_ISACT_1V8V,ICV_AUTOACTION_1V8V,SPARE43,SPARE44,ICV_P3V3V_DANGERUP,ICV_P3V3V_DANGERLOW,ICV_P3V3V_WARNUP,ICV_P3V3V_WARNLOW,ICV_ISACT_3V3V,ICV_AUTOACTION_3V3V,SPARE45,SPARE46,ICV_P2V5V_DANGERUP,ICV_P2V5V_DANGERLOW,ICV_P2V5V_WARNUP,ICV_P2V5V_WARNLOW,ICV_ISACT_2V5V,ICV_AUTOACTION_2V5V,SPARE47,SPARE48,ICV_P8V_DANGERUP,ICV_P8V_DANGERLOW,ICV_P8V_WARNUP,ICV_P8V_WARNLOW,ICV_ISACT_P8V,ICV_AUTOACTION_P8V,SPARE49,SPARE50,ICV_N8V_DANGERUP,ICV_N8V_DANGERLOW,ICV_N8V_WARNUP,ICV_N8V_WARNLOW,ICV_ISACT_N8V,ICV_AUTOACTION_N8V,SPARE51,SPARE52,ICV_TEMP_DANGERUP,ICV_TEMP_DANGERLOW,ICV_TEMP_WARNUP,ICV_TEMP_WARNLOW,ICV_ISACT_ICUTEMP,ICV_AUTOACTION_TEMP,SPARE53,SPARE54,ICV_P2V4_DANGERUP,ICV_P2V4_DANGERLOW,ICV_P2V4_WARNUP,ICV_P2V4_WARNLOW,ICV_ISACT_2V4V,ICV_AUTOACTION_2V4V,SPARE55,SPARE56,ICV_P1V5CUR_DANGERUP,ICV_P1V5CUR_DANGERLOW,ICV_P1V5CUR_WARNUP,ICV_P1V5CUR_WARNLOW,ICV_ISACT_1V5CUR,ICV_AUTOACTION_1V5I,SPARE57,SPARE58,ICV_P1V8CUR_DANGERUP,ICV_P1V8CUR_DANGERLOW,ICV_P1V8CUR_WARNUP,ICV_P1V8CUR_WARNLOW,ICV_ISACT_1V8CUR,ICV_AUTOACTION_1V8I,SPARE59,SPARE60,ICV_P3V3CUR_DANGERUP,ICV_P3V3CUR_DANGERLOW,ICV_P3V3CUR_WARNUP,ICV_P3V3CUR_WARNLOW,ICV_ISACT_3V3CUR,ICV_AUTOACTION_3V3I,SPARE61,SPARE62,ICV_P2V5CUR_DANGERUP,ICV_P2V5CUR_DANGERLOW,ICV_P2V5CUR_WARNUP,ICV_P2V5CUR_WARNLOW,ICV_ISACT_2V5CUR,ICV_AUTOACTION_2V5I,SPARE63,SPARE64,ICV_P8VCUR_DANGERUP,ICV_P8VCUR_DANGERLOW,ICV_P8VCUR_WARNUP,ICV_P8VCUR_WARNLOW,ICV_ISACT_P8VCUR,ICV_AUTOACTION_P8VI,SPARE65,SPARE66,ICV_N8VCUR_DANGERUP,ICV_N8VCUR_DANGERLOW,ICV_N8VCUR_WARNUP,ICV_N8VCUR_WARNLOW,ICV_ISACT_N8VCUR,ICV_AUTOACTION_N8VI,SPARE67,SPARE68,ICV_FOBTEMP_DANGERUP,ICV_FOBTEMP_DANGERLOW,ICV_FOBTEMP_WARNUP,ICV_FOBTEMP_WARNLOW,ICV_ISACT_FOBTEMP,ICV_AUTOACTION_FOBTEMP,SPARE69,SPARE70,ICV_FIBTEMP_DANGERUP,ICV_FIBTEMP_DANGERLOW,ICV_FIBTEMP_WARNUP,ICV_FIBTEMP_WARNLOW,ICV_ISACT_FIBTEMP,ICV_AUTOACTION_FIBTEMP,SPARE71,SPARE72,SPARE73,ICV_HKADCSAMP_BFDANG,ICV_HKADCSAMP_BFWARN,ICV_HKADCSPIMISS_BFDANG,ICV_HKADCACT_VOLT,ICV_HKADCACT_CUR,ICV_INTTRIG_PRM,ICV_INTTRIG_DUR,ICV_EXTTRIG_PRM,ICV_EXTTRIG_DUR,ICV_INTTRIG_EN,ICV_EXTTRIG_EN,ICV_IS_EN1,ICV_FIB_ICUARLIM,ICV_FIB_ICUARDEL,ICV_FIB_ICUARRG0,ICV_FIB_ICUARRG1,ICV_FIB_ICUARRG2,ICV_IS_EN2,ICV_FOB_ICUARLIM,ICV_FOB_ICUARDEL,ICV_FOB_ICUARRG0,ICV_FOB_ICUARRG1,ICV_FOB_ICUARRG2,ICV_FOB_FDIR_EN,ICV_FIB_FDIR_EN,ICV_FOB_ACTTRIES,ICV_FIB_ACTTRIES,ICV_FOB_WTTM,ICV_FIB_WTTM,ICV_GRCTM_FSINC,ICV_FEE_ACTDELLIM,ICV_HKADC_SPIMODE,ICV_FINETM_OFFSET,ICV_AUTOBT_ADDR,ICV_AUTOBT_DLY,ICV_AUTOBT_LEN,timestamp +0,0,1,1053,3,0,1563,444491492,0,1,0,3,25,15,1074264232,1074263328,2,3,0,254,0,1,0,25264347,0,0,0,0,0,0,0,0,254,0,1,0,25231579,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,256,0,0,0,1,301,0,0,0,0,2386578,1702992186,2147483677,1,1,0,1,1897,2279,2891,3174,3782,3755,2359,2028,1804,414,1663,1842,1082,838,2716,2702,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,16090,11,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,6945643455,13,255,0,0,0,1,2,5,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,500,1500,0,1,0,20,1073808244,0,1,0,60,1073807548,1,1,0,40,1073810444,1,1,0,172800,1073810104,0,1,0,172800,1073806664,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,7200,1073808880,0,0,0,1,0,0,0,0,0,2,0,8,240,8,4,1074263232,1074263136,0,2,0,2,960,10,8,0,1074263136,570490884,2,0,64,30,5,4,1074263280,1074263184,0,2,0,2,960,10,8,0,1074263184,570490883,10,1,0,5,0,60000,1024,1,1,3,3840,3840,46080,46080,32,64,0,64,64,0,2,7,7,7,7,11,1,0,3,10,5,20,29,12,15,0,0,0,0,10,1,1967,1768,1955,1793,1,1,0,0,2490,2116,2453,2154,1,1,0,0,3093,2577,2964,2706,1,1,0,0,3361,2863,3237,2988,1,1,0,0,4095,3183,3881,3260,1,1,0,0,4095,3165,3859,3242,1,1,0,0,3020,1968,2777,2008,1,1,0,0,3436,1718,3007,1804,1,0,0,0,2512,0,2512,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,3229,1991,3101,2033,1,1,0,0,3229,1991,3101,2033,1,1,0,0,0,2,1,5,1,1,0,600,0,600,0,0,0,3,19200,209715,1048575,1048575,0,3,19200,209715,1048575,1048575,0,0,10,10,120000,120000,180143985,838861,83050532,2294,4294967295,60,131072,2024-02-01 13:51:44.250162 +0,0,1,1053,3,1,1563,444491750,0,1,0,3,25,15,1074264232,1074263328,2,5,0,254,0,0,0,566984923,3,1694811998,998174,4294711005,4294937100,29130210903281,566984923,3,254,0,0,0,566952155,3,1694758886,105160,104598,4294836207,29130211197979,30081243,3,0,0,0,42,3,136,0,0,0,0,768,0,0,0,1,301,18,17,0,0,2174,0,444491749,1,1,0,1,1897,2278,2891,3174,3719,3695,2367,2024,1784,528,1815,1986,1708,1388,2716,2701,0,0,0,0,1,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,16090,10,0,0,1,0,0,0,30050,30050,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4430,64396,65397,464,453,64946,103,0,0,6945643455,13,255,0,0,0,1,2,5,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,500,1500,0,1,0,20,1073808244,0,1,0,60,1073807548,1,1,0,40,1073810444,1,1,0,172800,1073810104,0,1,0,172800,1073806664,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,7200,1073808880,0,0,0,1,0,0,0,0,0,2,0,128,15,4,2,1074263232,1074263136,0,2,0,2,960,10,8,0,1074263136,570490884,2,0,128,15,4,2,1074263280,1074263184,0,2,0,2,960,10,8,0,1074263184,570490883,10,1,0,5,0,60000,1024,1,1,3,3840,3840,46080,46080,32,64,0,64,64,0,2,7,7,7,7,11,1,0,3,10,5,20,29,12,15,0,0,0,0,10,1,1967,1768,1955,1793,1,1,0,0,2490,2116,2453,2154,1,1,0,0,3093,2577,2964,2706,1,1,0,0,3361,2863,3237,2988,1,1,0,0,4095,3183,3881,3260,1,1,0,0,4095,3165,3859,3242,1,1,0,0,3020,1968,2777,2008,1,1,0,0,3436,1718,3007,1804,1,0,0,0,2512,0,2512,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,3229,1991,3101,2033,1,1,0,0,3229,1991,3101,2033,1,1,0,0,0,2,1,5,1,1,0,600,0,600,0,0,1,3,38400,209715,1048575,1048575,1,3,38400,209715,1048575,1048575,0,0,15,15,30000,30000,180143985,838861,83050532,2294,4294967295,60,131072,2024-02-01 13:56:02.223877 +0,0,1,1053,3,2,1563,444491826,0,1,0,3,25,15,1074264232,1074263328,2,6,0,254,0,0,0,566984923,3,823207358,1001136,4294709567,4294934712,29130216146045,566984923,3,254,0,0,0,566952155,3,823154246,106165,104647,4294833539,29130216178605,30081243,3,0,0,0,82,226,6,0,0,0,0,768,0,0,0,1,301,20,19,0,0,2186,0,444491825,1,1,0,1,1897,2278,2891,3174,3719,3695,2369,2024,1784,518,1810,1984,1707,1388,2717,2701,0,0,0,0,1,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,16090,10,0,0,1,0,0,0,30050,30050,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4452,64392,65390,470,463,64938,179,0,0,6945643455,13,255,0,0,0,1,2,5,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,500,1500,0,1,0,20,1073808244,0,1,0,60,1073807548,1,1,0,40,1073810444,1,1,0,172800,1073810104,0,1,0,172800,1073806664,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,7200,1073808880,0,0,0,1,0,0,0,0,0,2,0,128,15,4,2,1074263232,1074263136,0,2,0,2,960,10,8,0,1074263136,570490884,2,0,128,15,4,2,1074263280,1074263184,0,2,0,2,960,10,8,0,1074263184,570490883,10,1,0,5,0,60000,1024,1,1,3,3840,3840,46080,46080,32,64,0,64,64,0,2,7,7,7,7,11,1,0,3,10,5,20,29,12,15,0,0,0,0,10,1,1967,1768,1955,1793,1,1,0,0,2490,2116,2453,2154,1,1,0,0,3093,2577,2964,2706,1,1,0,0,3361,2863,3237,2988,1,1,0,0,4095,3183,3881,3260,1,1,0,0,4095,3165,3859,3242,1,1,0,0,3020,1968,2777,2008,1,1,0,0,3436,1718,3007,1804,1,0,0,0,2512,0,2512,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,4095,0,4095,0,1,0,0,0,3229,1991,3101,2033,1,1,0,0,3229,1991,3101,2033,1,1,0,0,0,2,1,5,1,1,0,600,0,600,0,0,1,3,38400,209715,1048575,1048575,1,3,38400,209715,1048575,1048575,0,0,15,15,30000,30000,180143985,838861,83050532,2294,4294967295,60,131072,2024-02-01 13:57:18.172851 diff --git a/tests/data/Status.csv b/tests/data/Status.csv new file mode 100644 index 00000000..89fa0801 --- /dev/null +++ b/tests/data/Status.csv @@ -0,0 +1,7 @@ +PHVERNO,PHTYPE,PHSHF,PHAPID,PHGROUPF,PHSEQCNT,PHDLEN,SHCOARSE,PUS_SPARE1,PUS_VERSION,PUS_SPARE2,PUS_STYPE,PUS_SSUBTYPE,HK_STRUCID,MAGMODE,PRISENSOR,FOBCLKSRC,FIBCLKSRC,FOBSTAT,FIBSTAT,ICUFOBAR,ICUFIBAR,EDACSTAT,FOBAR,FIBAR,SPARE1,CPUIDLE,FILL,timestamp +0,0,1,1064,3,0,11,444491465,0,1,0,3,25,4,1,0,0,0,0,0,0,0,1,0,0,0,91,0,2024-02-01 13:51:17.066224 +0,0,1,1064,3,0,11,444491492,0,1,0,3,25,4,3,0,1,0,0,0,0,0,1,0,0,0,89,0,2024-02-01 13:51:44.125161 +0,0,1,1064,3,1,11,444491568,0,1,0,3,25,4,3,0,1,0,1,0,0,0,1,0,0,0,96,0,2024-02-01 13:53:00.010589 +0,0,1,1064,3,2,11,444491636,0,1,0,3,25,4,3,0,1,0,1,1,0,0,1,0,0,0,94,0,2024-02-01 13:54:08.013701 +0,0,1,1064,3,3,11,444491704,0,1,0,3,25,4,5,0,1,0,1,1,1,1,1,1,1,0,85,0,2024-02-01 13:55:16.013360 +0,0,1,1064,3,4,11,444491746,0,1,0,3,25,4,5,0,1,0,1,1,1,1,1,1,1,0,81,0,2024-02-01 13:55:58.017442 diff --git a/tests/unit/data/tHK.m b/tests/unit/data/tHK.m index cff33164..e53b90f7 100644 --- a/tests/unit/data/tHK.m +++ b/tests/unit/data/tHK.m @@ -1,6 +1,10 @@ classdef tHK < matlab.unittest.TestCase % THK Unit tests for "mag.HK" class. + properties (TestParameter) + HKTypes = {"SID15", "Processor", "Power", "Status"} + end + methods (Test) % Test that "crop" method crops data based on a "timerange" object. @@ -89,6 +93,27 @@ function getHKType(testCase) % Verify. testCase.verifyClass(hk, "mag.hk.Processor", "Correct type should be returned."); end + + % Test that all dependent properties of the HK types supported can + % be accessed. + function dependentProperties(~, HKTypes) + + % Set up. + fileName = fullfile(fileparts(mfilename("fullpath")), "../../data", HKTypes + ".csv"); + + data = readtimetable(fileName); + data.Properties.DimensionNames{1} = 't'; + + metaClass = meta.class.fromName("mag.hk." + HKTypes); + properties = metaClass.PropertyList; + + % Exercise and verify. + data = mag.hk.(HKTypes)(data, mag.meta.HK()); + + for p = properties([properties.Dependent] & cellfun(@(x) isequal(x, "public"), {properties.GetAccess}))' + data.(p.Name); + end + end end methods (Access = private) From 0fbb5fae68b34d165f97f28da238d857dce099ff Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Thu, 1 Feb 2024 17:26:57 +0000 Subject: [PATCH 13/21] Add test for `mag.Science` magnitude --- tests/unit/data/tScience.m | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/unit/data/tScience.m b/tests/unit/data/tScience.m index 50853359..b4250273 100644 --- a/tests/unit/data/tScience.m +++ b/tests/unit/data/tScience.m @@ -3,6 +3,19 @@ methods (Test) + % Test that magnetic field magnitude is computed correctly. + function magnitude(testCase) + + % Set up. + data = testCase.createTestData(); + + % Exercise. + magnitude = data.B; + + % Verify. + testCase.verifyEqual(magnitude, sqrt(sum([data.X.^2, data.Y.^2, data.Z.^2], 2)), "Magnitude should have expected values."); + end + % Test that "crop" method crops data based on a "duration" object. function cropMethod_duration(testCase) From 4d20a753201a0c465b0a970be8cd618f29c71a2f Mon Sep 17 00:00:00 2001 From: Michele Facchinelli Date: Fri, 2 Feb 2024 10:37:18 +0000 Subject: [PATCH 14/21] Add tests for derivatives, displays, and fix magnitude test --- resources/ReleaseNotes.md | 1 + src/data/+mag/+event/Event.m | 18 ++--- src/data/+mag/Science.m | 6 +- src/data/+mag/TimeSeries.m | 15 ++++- tests/unit/data/tHK.m | 50 ++++++++++++++ tests/unit/data/tScience.m | 126 ++++++++++++++++++++++++++++++++++- 6 files changed, 201 insertions(+), 15 deletions(-) diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index d390d21a..5afc91bd 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -11,6 +11,7 @@ - Science object with no data is considered empty in `mag.Instrument` - Reintroduce filtering for charts - Rename `cropDataBasedOnScience` to `cropToMatch` +- Make sure derivative is empty when data is empty - Fix issue with consecutive events of the same type missing a completion message - Fix issue when cropping data and no timestamps are selected - Fix issue with `mag.graphics.chart.Stem` not showing markers diff --git a/src/data/+mag/+event/Event.m b/src/data/+mag/+event/Event.m index a7210865..7413c219 100644 --- a/src/data/+mag/+event/Event.m +++ b/src/data/+mag/+event/Event.m @@ -36,13 +36,13 @@ methods (Hidden, Sealed) - function tableThis = timetable(this) + function timetableThis = timetable(this) % TIMETABLE Convert events to timetable. emptyTime = datetime.empty(); emptyTime.TimeZone = "UTC"; - tableThis = struct2table(struct(Time = emptyTime, ... + timetableThis = struct2table(struct(Time = emptyTime, ... Mode = double.empty(0, 1), ... PrimaryRate = double.empty(0, 1), ... SecondaryRate = double.empty(0, 1), ... @@ -51,21 +51,21 @@ Range = double.empty(0, 1), ... Sensor = string.empty(0, 1), ... Label = string.empty(0, 1))); - tableThis = table2timetable(tableThis, RowTimes = "Time"); + timetableThis = table2timetable(timetableThis, RowTimes = "Time"); for t = 1:numel(this) tt = this(t).convertToTimeTable(); - tableThis = outerjoin(tableThis, tt, MergeKeys = true, Keys = ["Time", intersect(tableThis.Properties.VariableNames, tt.Properties.VariableNames)]); + timetableThis = outerjoin(timetableThis, tt, MergeKeys = true, Keys = ["Time", intersect(timetableThis.Properties.VariableNames, tt.Properties.VariableNames)]); end - tableThis = sortrows(tableThis); + timetableThis = sortrows(timetableThis); - fillVariables = intersect(["Mode", "PrimaryRate", "SecondaryRate", "PacketFrequency", "Range"], tableThis.Properties.VariableNames); - tableThis(:, fillVariables) = fillmissing(tableThis(:, fillVariables), "previous"); + fillVariables = intersect(["Mode", "PrimaryRate", "SecondaryRate", "PacketFrequency", "Range"], timetableThis.Properties.VariableNames); + timetableThis(:, fillVariables) = fillmissing(timetableThis(:, fillVariables), "previous"); - tableThis{contains(tableThis.Label, "Config"), ["PrimaryRate", "SecondaryRate", "PacketFrequency", "Duration"]} = missing(); - tableThis{contains(tableThis.Label, "Ramp"), "Range"} = missing(); + timetableThis{contains(timetableThis.Label, "Config"), ["PrimaryRate", "SecondaryRate", "PacketFrequency", "Duration"]} = missing(); + timetableThis{contains(timetableThis.Label, "Ramp"), "Range"} = missing(); end end diff --git a/src/data/+mag/Science.m b/src/data/+mag/Science.m index 2c94afec..01247467 100644 --- a/src/data/+mag/Science.m +++ b/src/data/+mag/Science.m @@ -60,15 +60,15 @@ end function dx = get.dX(this) - dx = [diff(this.X); missing()]; + dx = this.computeDerivative(this.X); end function dy = get.dY(this) - dy = [diff(this.Y); missing()]; + dy = this.computeDerivative(this.Y); end function dz = get.dZ(this) - dz = [diff(this.Z); missing()]; + dz = this.computeDerivative(this.Z); end function range = get.Range(this) diff --git a/src/data/+mag/TimeSeries.m b/src/data/+mag/TimeSeries.m index 0bc3aa98..9f4048d4 100644 --- a/src/data/+mag/TimeSeries.m +++ b/src/data/+mag/TimeSeries.m @@ -22,7 +22,7 @@ end function dt = get.dT(this) - dt = [diff(this.Time); duration(missing())]; + dt = this.computeDerivative(this.Time); end function independentVariable = get.IndependentVariable(this) @@ -45,4 +45,17 @@ % DOWNSAMPLE Downsample data to the specified frequency. downsample(this, targetFrequency) end + + methods (Static, Access = protected) + + function dx = computeDerivative(x) + % COMPUTEDERIVATIVE Calculate numerical derivative. + + if isempty(x) + dx = feval(class(x) + ".empty"); + else + dx = vertcat(diff(x), missing()); + end + end + end end diff --git a/tests/unit/data/tHK.m b/tests/unit/data/tHK.m index e53b90f7..ab77a84b 100644 --- a/tests/unit/data/tHK.m +++ b/tests/unit/data/tHK.m @@ -81,6 +81,32 @@ function downsampleMethod_lowerFrequency(testCase) testCase.assertEqual(height(resampledData.DependentVariables), height(data.DependentVariables) / 2, "Frequency should be halved."); end + % Test that "getHKType" returns empty on empty input. + function getHKType_empty(testCase) + + % Set up. + data = mag.hk.Power.empty(); + + % Exercise. + hk = data.getHKType("PROCSTAT"); + + % Verify. + testCase.verifyEmpty(hk, "Empty should be returned for empty input."); + end + + % Test that "getHKType" method returns Power HK by default. + function getHKType_default(testCase) + + % Set up. + data = testCase.createTestData(); + + % Exercise. + hk = data.getHKType(); + + % Verify. + testCase.verifyEmpty(hk, "Empty should be returned when no such type exists."); + end + % Test that "getHKType" method selects the correct type. function getHKType(testCase) @@ -114,6 +140,30 @@ function dependentProperties(~, HKTypes) data.(p.Name); end end + + % Test that displaying a single object displays the correct + % information. + function customDisplay_singleObject(testCase) + + % Set up. + data = testCase.createTestData(); %#ok + + % Exercise. + output = evalc("display(data(1))"); + + % Verify. + testCase.verifySubstring(eraseTags(output), "Status HK (STATUS)", "HK meta data should be included in display."); + end + + % Test that displaying heterogeneous arrays does not error. + function customDisplay_heterogeneous(testCase) + + % Set up. + data = testCase.createTestData(); + + % Exercise and verify. + display(data); + end end methods (Access = private) diff --git a/tests/unit/data/tScience.m b/tests/unit/data/tScience.m index b4250273..7be460c3 100644 --- a/tests/unit/data/tScience.m +++ b/tests/unit/data/tScience.m @@ -1,6 +1,10 @@ classdef tScience < matlab.unittest.TestCase % TSCIENCE Unit tests for "mag.Science" class. + properties (TestParameter) + DerivativeVariable = {"dX", "dY", "dZ"} + end + methods (Test) % Test that magnetic field magnitude is computed correctly. @@ -9,11 +13,44 @@ function magnitude(testCase) % Set up. data = testCase.createTestData(); + expectedMagnitude = sqrt(sum([data.X.^2, data.Y.^2, data.Z.^2], 2)); + % Exercise. - magnitude = data.B; + actualMagnitude = data.B; % Verify. - testCase.verifyEqual(magnitude, sqrt(sum([data.X.^2, data.Y.^2, data.Z.^2], 2)), "Magnitude should have expected values."); + testCase.verifyThat(actualMagnitude, matlab.unittest.constraints.IsEqualTo(expectedMagnitude, Within = matlab.unittest.constraints.AbsoluteTolerance(1e-12)), ... + "Magnitude should have expected values."); + end + + % Test that derivative of an empty value is itself empty. + function derivative_empty(testCase, DerivativeVariable) + + % Set up. + data = testCase.createEmptyTestData(); + + % Exercise. + derivative = data.(DerivativeVariable); + + % Verify. + testCase.verifyEmpty(derivative, "Derivative of empty value should itself be empty."); + end + + % Test that derivative of a value is correct. + function derivative_nonEmpty(testCase, DerivativeVariable) + + % Set up. + data = testCase.createTestData(); + + v = erase(DerivativeVariable, "d"); + expectedDerivative = diff(data.(v)); + + % Exercise. + actualDerivative = data.(DerivativeVariable); + + % Verify. + testCase.verifyEqual(actualDerivative(1:end-1), expectedDerivative, "Derivative should match expected value."); + testCase.verifyTrue(ismissing(actualDerivative(end)), "Last element in derivative should be missing."); end % Test that "crop" method crops data based on a "duration" object. @@ -231,6 +268,75 @@ function replaceMethod_specified(testCase) testCase.verifyEqual(modifiedData.XYZ(1:4, :), zeros(4, 3), "Data within filter should be replaced."); testCase.verifyEqual(modifiedData.DependentVariables(5:end, :), data.DependentVariables(5:end, :), "Only data within filter should be replaced."); end + + % Test that displaying a single object displays the correct + % information. + function customDisplay_singleObject(testCase) + + % Set up. + data = testCase.createTestData(); + + data.MetaData.DataFrequency = 64; + data.MetaData.Mode = "Burst"; + data.MetaData.Model = "FM4"; + data.MetaData.Sensor = "FIB"; + + % Exercise. + output = evalc("display(data)"); + + % Verify. + testCase.verifySubstring(eraseTags(output), "FIB (FM4) in Burst (64)", "Science meta data should be included in display."); + end + + % Test that displaying a single object displays the correct + % information, even when model is missing. + function customDisplay_singleObject_noModel(testCase) + + % Set up. + data = testCase.createTestData(); + + data.MetaData.DataFrequency = 64; + data.MetaData.Mode = "Burst"; + data.MetaData.Model = string.empty(); + data.MetaData.Sensor = "FIB"; + + % Exercise. + output = evalc("display(data)"); + + % Verify. + testCase.verifySubstring(eraseTags(output), "FIB in Burst (64)", "Science meta data should be included in display."); + end + + % Test that displaying a single object displays the correct + % information, even when model and sensor are missing. + function customDisplay_singleObject_noSensor(testCase) + + % Set up. + data = testCase.createTestData(); + + data.MetaData.DataFrequency = 64; + data.MetaData.Mode = "Burst"; + data.MetaData.Model = string.empty(); + data.MetaData.Sensor = mag.meta.Sensor.empty(); + + % Exercise. + output = evalc("display(data)"); + + % Verify. + testCase.verifySubstring(eraseTags(output), "in Burst (64)", "Science meta data should be included in display."); + end + + % Test that displaying heterogeneous arrays does not error. + function customDisplay_heterogeneous(testCase) + + % Set up. + data = testCase.createTestData(); + + data = [data, data]; + + % Exercise and verify. + display(data); + end end methods (Access = private) @@ -253,6 +359,22 @@ function cropAndVerify(testCase, data, timeFilter, expectedTimes, expectedData) methods (Static, Access = private) + function [data, rawData] = createEmptyTestData() + + emptyTime = datetime.empty(); + emptyTime.TimeZone = "UTC"; + + rawData = struct2table(struct(Time = emptyTime, ... + x = double.empty(0, 1), ... + y = double.empty(0, 1), ... + z = double.empty(0, 1), ... + range = double.empty(0, 1), ... + sequence = double.empty(0, 1))); + rawData = table2timetable(rawData, RowTimes = "Time"); + + data = mag.Science(rawData, mag.meta.Science(Timestamp = datetime("now", TimeZone = "UTC"))); + end + function [data, rawData] = createTestData() rawData = timetable(datetime("now", TimeZone = "UTC") + minutes(1:10)', (1:10)', (11:20)', (21:30)', 3 * ones(10, 1), (1:10)', VariableNames = ["x", "y", "z", "range", "sequence"]); From 0dee10a313046aaeab3f1899fb6acc21fd22ed58 Mon Sep 17 00:00:00 2001 From: Michele Facchinelli Date: Fri, 2 Feb 2024 14:04:33 +0000 Subject: [PATCH 15/21] Add tests for `mag.PSD` and `computePSD` --- resources/ReleaseNotes.md | 1 + src/data/+mag/Science.m | 6 +- tests/unit/data/tPSD.m | 61 +++++++++ tests/unit/data/tScience.m | 267 ++++++++++++++++++++++--------------- 4 files changed, 222 insertions(+), 113 deletions(-) create mode 100644 tests/unit/data/tPSD.m diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index 5afc91bd..ea41e267 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -15,5 +15,6 @@ - Fix issue with consecutive events of the same type missing a completion message - Fix issue when cropping data and no timestamps are selected - Fix issue with `mag.graphics.chart.Stem` not showing markers +- Add tests for `mag.PSD` and `mag.Science/computePSD` - Add tests for `mag.graphics.mixin.mustBeColor` - Fix typo in `MarkerSupportTestCase` \ No newline at end of file diff --git a/src/data/+mag/Science.m b/src/data/+mag/Science.m index 01247467..12fb4aaf 100644 --- a/src/data/+mag/Science.m +++ b/src/data/+mag/Science.m @@ -236,11 +236,11 @@ function replace(this, timeFilter, filler) % Filter out data. if isempty(options.Start) - t = this.Data.t; + t = this.Time; locFilter = true(size(this.Data, 1), 1); else - t = (this.Data.t - options.Start); + t = (this.Time - options.Start); locFilter = t > 0; @@ -252,7 +252,7 @@ function replace(this, timeFilter, filler) % Compute PSD. dt = seconds(median(diff(t(locFilter)))); - [psd, f] = psdtsh(this.Data{locFilter, ["x", "y", "z"]}, dt, options.FFTType, options.NW); + [psd, f] = psdtsh(this.XYZ(locFilter, :), dt, options.FFTType, options.NW); psd = psd .^ 0.5; data = mag.PSD(table(f, psd(:, 1), psd(:, 2), psd(:, 3), VariableNames = ["f", "x", "y", "z"])); diff --git a/tests/unit/data/tPSD.m b/tests/unit/data/tPSD.m new file mode 100644 index 00000000..121b4eff --- /dev/null +++ b/tests/unit/data/tPSD.m @@ -0,0 +1,61 @@ +classdef tPSD < matlab.unittest.TestCase +% TSPD Unit tests for "mag.PSD" class. + + properties (TestParameter) + PropertyName = {struct(Public = "Frequency", Private = "f"), ... + struct(Public = "X", Private = "x"), ... + struct(Public = "Y", Private = "y"), ... + struct(Public = "Z", Private = "z")} + end + + methods (Test) + + % Test that independent variable of the PSD can be accessed. + function independentVariable(testCase) + + % Set up. + [psd, rawData] = testCase.createTestData(); + + % Exercise. + actualData = psd.IndependentVariable; + + % Verify. + testCase.verifyEqual(actualData, rawData{:, "f"}, "Independent property value should be as expected."); + end + + % Test that dependent variables of the PSD can be accessed. + function dependentVariables(testCase) + + % Set up. + [psd, rawData] = testCase.createTestData(); + + % Exercise. + actualData = psd.DependentVariables; + + % Verify. + testCase.verifyEqual(actualData, rawData{:, ["x", "y", "z"]}, "Dependent property value should be as expected."); + end + + % Test that dependent properties of the PSD can be accessed. + function dependentProperties(testCase, PropertyName) + + % Set up. + [psd, rawData] = testCase.createTestData(); + + % Exercise. + actualData = psd.(PropertyName.Public); + + % Verify. + testCase.verifyEqual(actualData, rawData{:, PropertyName.Private}, "Retireved property value should be as expected."); + end + end + + methods (Static, Access = private) + + function [psd, rawData] = createTestData() + + rawData = table((1:10)', (1:10)', (11:20)', (21:30)', VariableNames = ["f", "x", "y", "z"]); + psd = mag.PSD(rawData); + end + end +end diff --git a/tests/unit/data/tScience.m b/tests/unit/data/tScience.m index 7be460c3..23cced36 100644 --- a/tests/unit/data/tScience.m +++ b/tests/unit/data/tScience.m @@ -11,12 +11,12 @@ function magnitude(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - expectedMagnitude = sqrt(sum([data.X.^2, data.Y.^2, data.Z.^2], 2)); + expectedMagnitude = sqrt(sum([science.X.^2, science.Y.^2, science.Z.^2], 2)); % Exercise. - actualMagnitude = data.B; + actualMagnitude = science.B; % Verify. testCase.verifyThat(actualMagnitude, matlab.unittest.constraints.IsEqualTo(expectedMagnitude, Within = matlab.unittest.constraints.AbsoluteTolerance(1e-12)), ... @@ -27,10 +27,10 @@ function magnitude(testCase) function derivative_empty(testCase, DerivativeVariable) % Set up. - data = testCase.createEmptyTestData(); + science = testCase.createEmptyTestData(); % Exercise. - derivative = data.(DerivativeVariable); + derivative = science.(DerivativeVariable); % Verify. testCase.verifyEmpty(derivative, "Derivative of empty value should itself be empty."); @@ -40,13 +40,13 @@ function derivative_empty(testCase, DerivativeVariable) function derivative_nonEmpty(testCase, DerivativeVariable) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); v = erase(DerivativeVariable, "d"); - expectedDerivative = diff(data.(v)); + expectedDerivative = diff(science.(v)); % Exercise. - actualDerivative = data.(DerivativeVariable); + actualDerivative = science.(DerivativeVariable); % Verify. testCase.verifyEqual(actualDerivative(1:end-1), expectedDerivative, "Derivative should match expected value."); @@ -57,114 +57,114 @@ function derivative_nonEmpty(testCase, DerivativeVariable) function cropMethod_duration(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - expectedTimes = data.Time(2:end); - expectedData = data.DependentVariables(2:end, :); + expectedTimes = science.Time(2:end); + expectedData = science.DependentVariables(2:end, :); timeFilter = minutes(1); % Exercise and verify. - testCase.cropAndVerify(data, timeFilter, expectedTimes, expectedData); + testCase.cropAndVerify(science, timeFilter, expectedTimes, expectedData); end % Test that "crop" method crops data based on a "timerange" object. function cropMethod_timerange(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - expectedTimes = data.Time(3:end); - expectedData = data.DependentVariables(3:end, :); + expectedTimes = science.Time(3:end); + expectedData = science.DependentVariables(3:end, :); - timeFilter = timerange(data.Time(2), data.Time(end), "openleft"); + timeFilter = timerange(science.Time(2), science.Time(end), "openleft"); % Exercise and verify. - testCase.cropAndVerify(data, timeFilter, expectedTimes, expectedData); + testCase.cropAndVerify(science, timeFilter, expectedTimes, expectedData); end % Test that "crop" method crops data based on a "withtol" object. function cropMethod_withtol(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - expectedTimes = data.Time(4:6); - expectedData = data.DependentVariables(4:6, :); + expectedTimes = science.Time(4:6); + expectedData = science.DependentVariables(4:6, :); - timeFilter = withtol(data.Time(5), minutes(1)); + timeFilter = withtol(science.Time(5), minutes(1)); % Exercise and verify. - testCase.cropAndVerify(data, timeFilter, expectedTimes, expectedData); + testCase.cropAndVerify(science, timeFilter, expectedTimes, expectedData); end % Test that "crop" method also crops events. function cropMethod_events(testCase) % Set up. - data = testCase.createTestData(); - data.Data.Properties.Events = eventtable(data.Data); + science = testCase.createTestData(); + science.Data.Properties.Events = eventtable(science.Data); % Exercise. - data.crop(minutes(1)); + science.crop(minutes(1)); % Verify. - testCase.assertEqual(height(data.Events.Time), height(data.Time), "Data should be cropped as expected."); - testCase.verifyEqual(data.Events.Time, data.Time, "Data should be cropped as expected."); + testCase.assertEqual(height(science.Events.Time), height(science.Time), "Data should be cropped as expected."); + testCase.verifyEqual(science.Events.Time, science.Time, "Data should be cropped as expected."); end % Test that "crop" method does not fail when no data is selected. function cropMethod_noSelection(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); % Exercise. - data.crop(timerange(datetime("Inf", TimeZone = "local"), datetime("-Inf", TimeZone = "local"))); + science.crop(timerange(datetime("Inf", TimeZone = "local"), datetime("-Inf", TimeZone = "local"))); % Verify. - testCase.verifyEmpty(data.IndependentVariable, "All data should be cropped out."); - testCase.verifyEmpty(data.DependentVariables, "All data should be cropped out."); + testCase.verifyEmpty(science.IndependentVariable, "All data should be cropped out."); + testCase.verifyEmpty(science.DependentVariables, "All data should be cropped out."); - testCase.verifyTrue(ismissing(data.MetaData.Timestamp), "All data should be cropped out."); + testCase.verifyTrue(ismissing(science.MetaData.Timestamp), "All data should be cropped out."); end % Test that "resample" method can resample to a lower frequency. function resampleMethod_lowerFrequency(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - initialFrequency = 1 / seconds(mode(data.dT)); + initialFrequency = 1 / seconds(mode(science.dT)); % Exercise. - resampledData = data.copy(); + resampledData = science.copy(); resampledData.resample(initialFrequency / 2); % Verify. - testCase.assertEqual(height(resampledData.IndependentVariable), height(data.Time) / 2, "Frequency should be halved."); - testCase.verifyEqual(resampledData.Time, (data.Time(1):minutes(2):data.Time(end))', "Frequency should be halved."); + testCase.assertEqual(height(resampledData.IndependentVariable), height(science.Time) / 2, "Frequency should be halved."); + testCase.verifyEqual(resampledData.Time, (science.Time(1):minutes(2):science.Time(end))', "Frequency should be halved."); - testCase.assertEqual(height(resampledData.DependentVariables), height(data.DependentVariables) / 2, "Frequency should be halved."); + testCase.assertEqual(height(resampledData.DependentVariables), height(science.DependentVariables) / 2, "Frequency should be halved."); end % Test that "resample" method can resample to a higher frequency. function resampleMethod_higherFrequency(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - initialFrequency = 1 / seconds(mode(data.dT)); + initialFrequency = 1 / seconds(mode(science.dT)); % Exercise. - resampledData = data.copy(); + resampledData = science.copy(); resampledData.resample(2 * initialFrequency); % Verify. - testCase.assertEqual(height(resampledData.IndependentVariable), (2 * height(data.Time)) - 1, "Frequency should be doubled."); - testCase.verifyEqual(resampledData.Time, (data.Time(1):seconds(30):data.Time(end))', "Frequency should be doubled."); + testCase.assertEqual(height(resampledData.IndependentVariable), (2 * height(science.Time)) - 1, "Frequency should be doubled."); + testCase.verifyEqual(resampledData.Time, (science.Time(1):seconds(30):science.Time(end))', "Frequency should be doubled."); - testCase.assertEqual(height(resampledData.DependentVariables), (2 * height(data.DependentVariables)) - 1, "Frequency should be doubled."); + testCase.assertEqual(height(resampledData.DependentVariables), (2 * height(science.DependentVariables)) - 1, "Frequency should be doubled."); end % Test that "resample" method throws when target frequency is not @@ -172,29 +172,29 @@ function resampleMethod_higherFrequency(testCase) function resampleMethod_error(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); % Exercise and verify. - testCase.verifyError(@() data.resample(0.12345), "", "Resampling should error when frequencies do not match."); + testCase.verifyError(@() science.resample(0.12345), "", "Resampling should error when frequencies do not match."); end % Test that "downsample" method can downsample to a lower frequency. function downsampleMethod_lowerFrequency(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - initialFrequency = 1 / seconds(mode(data.dT)); + initialFrequency = 1 / seconds(mode(science.dT)); % Exercise. - downsampledData = data.copy(); + downsampledData = science.copy(); downsampledData.downsample(initialFrequency / 2); % Verify. - testCase.assertEqual(height(downsampledData.IndependentVariable), height(data.Time) / 2, "Frequency should be halved."); - testCase.verifyEqual(downsampledData.Time, (data.Time(1):minutes(2):data.Time(end))', "Frequency should be halved."); + testCase.assertEqual(height(downsampledData.IndependentVariable), height(science.Time) / 2, "Frequency should be halved."); + testCase.verifyEqual(downsampledData.Time, (science.Time(1):minutes(2):science.Time(end))', "Frequency should be halved."); - testCase.assertEqual(height(downsampledData.DependentVariables), height(data.DependentVariables) / 2, "Frequency should be halved."); + testCase.assertEqual(height(downsampledData.DependentVariables), height(science.DependentVariables) / 2, "Frequency should be halved."); testCase.verifyTrue(any(ismissing(downsampledData.XYZ), "all"), "Initial data should be replaced with missing values to account for filter warm-up."); end @@ -203,10 +203,10 @@ function downsampleMethod_lowerFrequency(testCase) function downsampleMethod_error(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); % Exercise and verify. - testCase.verifyError(@() data.downsample(0.12345), "", "Resampling should error when frequencies do not match."); + testCase.verifyError(@() science.downsample(0.12345), "", "Resampling should error when frequencies do not match."); end % Test that "filter" method can filter with a "digitalFilter" @@ -214,40 +214,40 @@ function downsampleMethod_error(testCase) function filterMethod_digitalFilter(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - initialFrequency = 1 / seconds(mode(data.dT)); + initialFrequency = 1 / seconds(mode(science.dT)); filter = designfilt("lowpassfir", SampleRate = initialFrequency, PassbandFrequency = (initialFrequency / 4), StopbandFrequency = 1.5 * (initialFrequency / 4)); % Exercise. - downsampledData = data.copy(); - downsampledData.filter(filter); + downsampledScience = science.copy(); + downsampledScience.filter(filter); % Verify. - testCase.assertSize(downsampledData.IndependentVariable, size(data.Time), "Filtering should not affect size."); - testCase.verifyEqual(downsampledData.Time, data.Time, "Filtering should not affect time."); + testCase.assertSize(downsampledScience.IndependentVariable, size(science.Time), "Filtering should not affect size."); + testCase.verifyEqual(downsampledScience.Time, science.Time, "Filtering should not affect time."); - testCase.assertSize(downsampledData.DependentVariables, size(data.DependentVariables), "Filtering should not affect size."); - testCase.verifyTrue(all(ismissing(downsampledData.XYZ), "all"), "All data should be replaced with missing values to account for filter warm-up."); + testCase.assertSize(downsampledScience.DependentVariables, size(science.DependentVariables), "Filtering should not affect size."); + testCase.verifyTrue(all(ismissing(downsampledScience.XYZ), "all"), "All data should be replaced with missing values to account for filter warm-up."); end % Test that "replace" method replaces data with default filler. function replaceMethod_default(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); % Exercise. - modifiedData = data.copy(); - modifiedData.replace(minutes(3)); + modifiedScience = science.copy(); + modifiedScience.replace(minutes(3)); % Verify. - testCase.assertSize(modifiedData.IndependentVariable, size(data.Time), "Time should not be modified."); - testCase.verifyEqual(modifiedData.Time, data.Time, "Time should not be modified."); + testCase.assertSize(modifiedScience.IndependentVariable, size(science.Time), "Time should not be modified."); + testCase.verifyEqual(modifiedScience.Time, science.Time, "Time should not be modified."); - testCase.assertSize(modifiedData.DependentVariables, size(data.DependentVariables), "Data size should not change."); - testCase.verifyTrue(all(ismissing(modifiedData.XYZ(1:4, :)), "all"), "Data within filter should be replaced."); - testCase.verifyEqual(modifiedData.DependentVariables(5:end, :), data.DependentVariables(5:end, :), "Only data within filter should be replaced."); + testCase.assertSize(modifiedScience.DependentVariables, size(science.DependentVariables), "Data size should not change."); + testCase.verifyTrue(all(ismissing(modifiedScience.XYZ(1:4, :)), "all"), "Data within filter should be replaced."); + testCase.verifyEqual(modifiedScience.DependentVariables(5:end, :), science.DependentVariables(5:end, :), "Only data within filter should be replaced."); end % Test that "replace" method replaces data with default filler. @@ -257,16 +257,49 @@ function replaceMethod_specified(testCase) data = testCase.createTestData(); % Exercise. - modifiedData = data.copy(); - modifiedData.replace(minutes(3), 0); + modifiedScience = data.copy(); + modifiedScience.replace(minutes(3), 0); % Verify. - testCase.assertSize(modifiedData.IndependentVariable, size(data.Time), "Time should not be modified."); - testCase.verifyEqual(modifiedData.Time, data.Time, "Time should not be modified."); + testCase.assertSize(modifiedScience.IndependentVariable, size(data.Time), "Time should not be modified."); + testCase.verifyEqual(modifiedScience.Time, data.Time, "Time should not be modified."); - testCase.assertSize(modifiedData.DependentVariables, size(data.DependentVariables), "Data size should not change."); - testCase.verifyEqual(modifiedData.XYZ(1:4, :), zeros(4, 3), "Data within filter should be replaced."); - testCase.verifyEqual(modifiedData.DependentVariables(5:end, :), data.DependentVariables(5:end, :), "Only data within filter should be replaced."); + testCase.assertSize(modifiedScience.DependentVariables, size(data.DependentVariables), "Data size should not change."); + testCase.verifyEqual(modifiedScience.XYZ(1:4, :), zeros(4, 3), "Data within filter should be replaced."); + testCase.verifyEqual(modifiedScience.DependentVariables(5:end, :), data.DependentVariables(5:end, :), "Only data within filter should be replaced."); + end + + % Test that PSD can detect sine wave frequency, with default + % options. + function computePSD_sineWave_default(testCase) + + % Set up. + science = testCase.createSineWaveTestData(); + + % Execute. + psd = science.computePSD(); + + % Verify. + [~, idxMax] = max([psd.X, psd.Y, psd.Z]); + + testCase.verifyEqual(psd.Frequency(idxMax), [50; 100; 150], "PSD max frequency should match sine wave frequency."); + end + + % Test that PSD can detect sine wave frequency, with selected data + % only. + function computePSD_sineWave_startAndDuration(testCase) + + % Set up. + science = testCase.createSineWaveTestData(); + + % Execute. + psd = science.computePSD(Start = science.Time(10), Duration = milliseconds(500)); + + % Verify. + [~, idxMax] = max([psd.X, psd.Y, psd.Z]); + + testCase.verifyThat(psd.Frequency(idxMax), matlab.unittest.constraints.IsEqualTo([50; 100; 150], Within = matlab.unittest.constraints.RelativeTolerance(0.1)), ... + "PSD max frequency should match sine wave frequency."); end % Test that displaying a single object displays the correct @@ -274,15 +307,15 @@ function replaceMethod_specified(testCase) function customDisplay_singleObject(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - data.MetaData.DataFrequency = 64; - data.MetaData.Mode = "Burst"; - data.MetaData.Model = "FM4"; - data.MetaData.Sensor = "FIB"; + science.MetaData.DataFrequency = 64; + science.MetaData.Mode = "Burst"; + science.MetaData.Model = "FM4"; + science.MetaData.Sensor = "FIB"; % Exercise. - output = evalc("display(data)"); + output = evalc("display(science)"); % Verify. testCase.verifySubstring(eraseTags(output), "FIB (FM4) in Burst (64)", "Science meta data should be included in display."); @@ -293,15 +326,15 @@ function customDisplay_singleObject(testCase) function customDisplay_singleObject_noModel(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - data.MetaData.DataFrequency = 64; - data.MetaData.Mode = "Burst"; - data.MetaData.Model = string.empty(); - data.MetaData.Sensor = "FIB"; + science.MetaData.DataFrequency = 64; + science.MetaData.Mode = "Burst"; + science.MetaData.Model = string.empty(); + science.MetaData.Sensor = "FIB"; % Exercise. - output = evalc("display(data)"); + output = evalc("display(science)"); % Verify. testCase.verifySubstring(eraseTags(output), "FIB in Burst (64)", "Science meta data should be included in display."); @@ -312,15 +345,15 @@ function customDisplay_singleObject_noModel(testCase) function customDisplay_singleObject_noSensor(testCase) % Set up. - data = testCase.createTestData(); + science = testCase.createTestData(); - data.MetaData.DataFrequency = 64; - data.MetaData.Mode = "Burst"; - data.MetaData.Model = string.empty(); - data.MetaData.Sensor = mag.meta.Sensor.empty(); + science.MetaData.DataFrequency = 64; + science.MetaData.Mode = "Burst"; + science.MetaData.Model = string.empty(); + science.MetaData.Sensor = mag.meta.Sensor.empty(); % Exercise. - output = evalc("display(data)"); + output = evalc("display(science)"); % Verify. testCase.verifySubstring(eraseTags(output), "in Burst (64)", "Science meta data should be included in display."); @@ -330,36 +363,35 @@ function customDisplay_singleObject_noSensor(testCase) function customDisplay_heterogeneous(testCase) % Set up. - data = testCase.createTestData(); - - data = [data, data]; + science = testCase.createTestData(); + science = [science, science]; %#ok % Exercise and verify. - display(data); + evalc("display(science)"); end end methods (Access = private) - function cropAndVerify(testCase, data, timeFilter, expectedTimes, expectedData) + function cropAndVerify(testCase, science, timeFilter, expectedTimes, expectedData) % Exercise. - data.crop(timeFilter); + science.crop(timeFilter); % Verify. - testCase.assertSize(data.IndependentVariable, size(expectedTimes), "Data should be cropped as expected."); - testCase.verifyEqual(data.Time, expectedTimes, "Data should be cropped as expected."); + testCase.assertSize(science.IndependentVariable, size(expectedTimes), "Data should be cropped as expected."); + testCase.verifyEqual(science.Time, expectedTimes, "Data should be cropped as expected."); - testCase.assertSize(data.DependentVariables, size(expectedData), "Data should be cropped as expected."); - testCase.verifyEqual(data.DependentVariables, expectedData, "Data should be cropped as expected."); + testCase.assertSize(science.DependentVariables, size(expectedData), "Data should be cropped as expected."); + testCase.verifyEqual(science.DependentVariables, expectedData, "Data should be cropped as expected."); - testCase.verifyEqual(data.MetaData.Timestamp, data.Time(1), "Meta data timestamp should be updated."); + testCase.verifyEqual(science.MetaData.Timestamp, science.Time(1), "Meta data timestamp should be updated."); end end methods (Static, Access = private) - function [data, rawData] = createEmptyTestData() + function [science, rawData] = createEmptyTestData() emptyTime = datetime.empty(); emptyTime.TimeZone = "UTC"; @@ -372,13 +404,28 @@ function cropAndVerify(testCase, data, timeFilter, expectedTimes, expectedData) sequence = double.empty(0, 1))); rawData = table2timetable(rawData, RowTimes = "Time"); - data = mag.Science(rawData, mag.meta.Science(Timestamp = datetime("now", TimeZone = "UTC"))); + science = mag.Science(rawData, mag.meta.Science(Timestamp = datetime("now", TimeZone = "UTC"))); end - function [data, rawData] = createTestData() + function [science, rawData] = createTestData() rawData = timetable(datetime("now", TimeZone = "UTC") + minutes(1:10)', (1:10)', (11:20)', (21:30)', 3 * ones(10, 1), (1:10)', VariableNames = ["x", "y", "z", "range", "sequence"]); - data = mag.Science(rawData, mag.meta.Science(Timestamp = datetime("now", TimeZone = "UTC"))); + science = mag.Science(rawData, mag.meta.Science(Timestamp = datetime("now", TimeZone = "UTC"))); + end + + function [science, rawData] = createSineWaveTestData() + + num = 1000; + + timestamp = datetime("now", TimeZone = "UTC") + milliseconds(1:num)'; + + t = seconds(timestamp - timestamp(1)); + x = sin(100 * pi * t); + y = sin(200 * pi * t); + z = sin(300 * pi * t); + + rawData = timetable(timestamp, x, y, z, 3 * ones(num, 1), (1:num)', VariableNames = ["x", "y", "z", "range", "sequence"]); + science = mag.Science(rawData, mag.meta.Science(Timestamp = datetime("now", TimeZone = "UTC"))); end end end From 97c0a61a506c91a23905d9c93b1af164e685aeb9 Mon Sep 17 00:00:00 2001 From: Michele Facchinelli Date: Fri, 2 Feb 2024 14:04:52 +0000 Subject: [PATCH 16/21] Add more tests for HK display --- tests/unit/data/tData.m | 6 +- tests/unit/data/tHK.m | 129 +++++++++++++++++++++++----------------- 2 files changed, 79 insertions(+), 56 deletions(-) diff --git a/tests/unit/data/tData.m b/tests/unit/data/tData.m index d4b8b197..8cbb84b4 100644 --- a/tests/unit/data/tData.m +++ b/tests/unit/data/tData.m @@ -89,11 +89,11 @@ function copyMethod(testCase) data = testCase.createTestData(); % Exercise. - dataCopy = data.copy(); + copiedData = data.copy(); % Verify. - testCase.verifyNotSameHandle(data, dataCopy, "Copied data should be different instance."); - testCase.verifyNotSameHandle(data.MetaData, dataCopy.MetaData, "Copied data should be different instance."); + testCase.verifyNotSameHandle(data, copiedData, "Copied data should be different instance."); + testCase.verifyNotSameHandle(data.MetaData, copiedData.MetaData, "Copied data should be different instance."); end end diff --git a/tests/unit/data/tHK.m b/tests/unit/data/tHK.m index ab77a84b..83c04d71 100644 --- a/tests/unit/data/tHK.m +++ b/tests/unit/data/tHK.m @@ -11,33 +11,33 @@ function cropMethod_timerange(testCase) % Set up. - data = testCase.createTestData(); + hk = testCase.createTestData(); - expectedTimes = {data(1).Time(3:end), data(2).Time(2:end)}; - expectedData = {data(1).DependentVariables(3:end, :), data(2).DependentVariables(2:end, :)}; + expectedTimes = {hk(1).Time(3:end), hk(2).Time(2:end)}; + expectedData = {hk(1).DependentVariables(3:end, :), hk(2).DependentVariables(2:end, :)}; - timeFilter = timerange(data(1).Time(2), data(1).Time(end), "openleft"); + timeFilter = timerange(hk(1).Time(2), hk(1).Time(end), "openleft"); % Exercise and verify. - testCase.cropAndVerify(data, timeFilter, expectedTimes, expectedData); + testCase.cropAndVerify(hk, timeFilter, expectedTimes, expectedData); end % Test that "crop" method does not fail when no data is selected. function cropMethod_noSelection(testCase) % Set up. - data = testCase.createTestData(); + hk = testCase.createTestData(); % Exercise. - data.crop(timerange(datetime("Inf", TimeZone = "local"), datetime("-Inf", TimeZone = "local"))); + hk.crop(timerange(datetime("Inf", TimeZone = "local"), datetime("-Inf", TimeZone = "local"))); % Verify. - for i = 1:numel(data) + for i = 1:numel(hk) - testCase.verifyEmpty(data(i).IndependentVariable, "All data should be cropped out."); - testCase.verifyEmpty(data(i).DependentVariables, "All data should be cropped out."); + testCase.verifyEmpty(hk(i).IndependentVariable, "All data should be cropped out."); + testCase.verifyEmpty(hk(i).DependentVariables, "All data should be cropped out."); - testCase.verifyTrue(ismissing(data(i).MetaData.Timestamp), "All data should be cropped out."); + testCase.verifyTrue(ismissing(hk(i).MetaData.Timestamp), "All data should be cropped out."); end end @@ -45,79 +45,79 @@ function cropMethod_noSelection(testCase) function resampleMethod_higherFrequency(testCase) % Set up. - data = testCase.createTestData(); - data = data(1); + hk = testCase.createTestData(); + hk = hk(1); - initialFrequency = 1 / seconds(mode(data.dT)); + initialFrequency = 1 / seconds(mode(hk.dT)); % Exercise. - resampledData = data.copy(); + resampledData = hk.copy(); resampledData.resample(2 * initialFrequency); % Verify. - testCase.assertEqual(height(resampledData.IndependentVariable), (2 * height(data.Time)) - 1, "Frequency should be halved."); - testCase.verifyEqual(resampledData.Time, (data.Time(1):seconds(30):data.Time(end))', "Frequency should be halved."); + testCase.assertEqual(height(resampledData.IndependentVariable), (2 * height(hk.Time)) - 1, "Frequency should be halved."); + testCase.verifyEqual(resampledData.Time, (hk.Time(1):seconds(30):hk.Time(end))', "Frequency should be halved."); - testCase.assertEqual(height(resampledData.DependentVariables), (2 * height(data.DependentVariables)) - 1, "Frequency should be halved."); + testCase.assertEqual(height(resampledData.DependentVariables), (2 * height(hk.DependentVariables)) - 1, "Frequency should be halved."); end % Test that "downsample" method can resample to a lower frequency. function downsampleMethod_lowerFrequency(testCase) % Set up. - data = testCase.createTestData(); - data = data(1); + hk = testCase.createTestData(); + hk = hk(1); - initialFrequency = 1 / seconds(mode(data.dT)); + initialFrequency = 1 / seconds(mode(hk.dT)); % Exercise. - resampledData = data.copy(); + resampledData = hk.copy(); resampledData.downsample(initialFrequency / 2); % Verify. - testCase.assertEqual(height(resampledData.IndependentVariable), height(data.Time) / 2, "Frequency should be halved."); - testCase.verifyEqual(resampledData.Time, (data.Time(1):minutes(2):data.Time(end))', "Frequency should be halved."); + testCase.assertEqual(height(resampledData.IndependentVariable), height(hk.Time) / 2, "Frequency should be halved."); + testCase.verifyEqual(resampledData.Time, (hk.Time(1):minutes(2):hk.Time(end))', "Frequency should be halved."); - testCase.assertEqual(height(resampledData.DependentVariables), height(data.DependentVariables) / 2, "Frequency should be halved."); + testCase.assertEqual(height(resampledData.DependentVariables), height(hk.DependentVariables) / 2, "Frequency should be halved."); end % Test that "getHKType" returns empty on empty input. function getHKType_empty(testCase) % Set up. - data = mag.hk.Power.empty(); + hk = mag.hk.Power.empty(); % Exercise. - hk = data.getHKType("PROCSTAT"); + selectedHK = hk.getHKType("PROCSTAT"); % Verify. - testCase.verifyEmpty(hk, "Empty should be returned for empty input."); + testCase.verifyEmpty(selectedHK, "Empty should be returned for empty input."); end % Test that "getHKType" method returns Power HK by default. function getHKType_default(testCase) % Set up. - data = testCase.createTestData(); + hk = testCase.createTestData(); % Exercise. - hk = data.getHKType(); + selectedHK = hk.getHKType(); % Verify. - testCase.verifyEmpty(hk, "Empty should be returned when no such type exists."); + testCase.verifyEmpty(selectedHK, "Empty should be returned when no such type exists."); end % Test that "getHKType" method selects the correct type. function getHKType(testCase) % Set up. - data = testCase.createTestData(); + hk = testCase.createTestData(); % Exercise. - hk = data.getHKType("PROCSTAT"); + selectedHK = hk.getHKType("PROCSTAT"); % Verify. - testCase.verifyClass(hk, "mag.hk.Processor", "Correct type should be returned."); + testCase.verifyClass(selectedHK, "mag.hk.Processor", "Correct type should be returned."); end % Test that all dependent properties of the HK types supported can @@ -127,29 +127,52 @@ function dependentProperties(~, HKTypes) % Set up. fileName = fullfile(fileparts(mfilename("fullpath")), "../../data", HKTypes + ".csv"); - data = readtimetable(fileName); - data.Properties.DimensionNames{1} = 't'; + hk = readtimetable(fileName); + hk.Properties.DimensionNames{1} = 't'; metaClass = meta.class.fromName("mag.hk." + HKTypes); properties = metaClass.PropertyList; % Exercise and verify. - data = mag.hk.(HKTypes)(data, mag.meta.HK()); + hk = mag.hk.(HKTypes)(hk, mag.meta.HK()); for p = properties([properties.Dependent] & cellfun(@(x) isequal(x, "public"), {properties.GetAccess}))' - data.(p.Name); + hk.(p.Name); end end + % Test that displaying a deleted handle does not error. + function customDisplay_deleted(testCase) + + % Set up. + hk = testCase.createTestData(); + hk = hk(1); + + delete(hk); + + % Exercise and verify. + evalc("display(hk)"); + end + + % Test that displaying an empty array does not error. + function customDisplay_empty(~) + + % Set up. + hk = mag.hk.Power.empty(); %#ok + + % Exercise and verify. + evalc("display(hk)"); + end + % Test that displaying a single object displays the correct % information. function customDisplay_singleObject(testCase) % Set up. - data = testCase.createTestData(); %#ok + hk = testCase.createTestData(); %#ok % Exercise. - output = evalc("display(data(1))"); + output = evalc("display(hk(1))"); % Verify. testCase.verifySubstring(eraseTags(output), "Status HK (STATUS)", "HK meta data should be included in display."); @@ -159,45 +182,45 @@ function customDisplay_singleObject(testCase) function customDisplay_heterogeneous(testCase) % Set up. - data = testCase.createTestData(); + hk = testCase.createTestData(); %#ok % Exercise and verify. - display(data); + evalc("display(hk)"); end end methods (Access = private) - function cropAndVerify(testCase, data, timeFilter, expectedTimes, expectedData) + function cropAndVerify(testCase, hk, timeFilter, expectedTimes, expectedData) % Exercise. - data.crop(timeFilter); + hk.crop(timeFilter); % Verify. - for i = 1:numel(data) + for i = 1:numel(hk) - testCase.assertSize(data(i).IndependentVariable, size(expectedTimes{i}), "Data should be cropped as expected."); - testCase.verifyEqual(data(i).Time, expectedTimes{i}, "Data should be cropped as expected."); + testCase.assertSize(hk(i).IndependentVariable, size(expectedTimes{i}), "Data should be cropped as expected."); + testCase.verifyEqual(hk(i).Time, expectedTimes{i}, "Data should be cropped as expected."); - testCase.assertSize(data(i).DependentVariables, size(expectedData{i}), "Data should be cropped as expected."); - testCase.verifyEqual(data(i).DependentVariables, expectedData{i}, "Data should be cropped as expected."); + testCase.assertSize(hk(i).DependentVariables, size(expectedData{i}), "Data should be cropped as expected."); + testCase.verifyEqual(hk(i).DependentVariables, expectedData{i}, "Data should be cropped as expected."); - testCase.verifyEqual(data(i).MetaData.Timestamp, data(i).Time(1), "Meta data timestamp should be updated."); + testCase.verifyEqual(hk(i).MetaData.Timestamp, hk(i).Time(1), "Meta data timestamp should be updated."); end end end methods (Static, Access = private) - function data = createTestData() + function hk = createTestData() timestamps = datetime("now", TimeZone = "UTC") + minutes(1:10)'; statusData = timetable(timestamps, ones(10, 1), zeros(10, 1), VariableNames = ["FOBSTAT", "FIBSTAT"]); procstatData = timetable(timestamps(1:2:end), (1:5)', (11:15)', VariableNames = ["OBNQ_NUM_MSG", "IBNQ_NUM_MSG"]); - data(1) = mag.hk.Status(statusData, mag.meta.HK(Type = "STATUS")); - data(2) = mag.hk.Processor(procstatData, mag.meta.HK(Type = "PROCSTAT")); + hk(1) = mag.hk.Status(statusData, mag.meta.HK(Type = "STATUS")); + hk(2) = mag.hk.Processor(procstatData, mag.meta.HK(Type = "PROCSTAT")); end end end From a56684619c12271a91a9398be70b638a0e094901 Mon Sep 17 00:00:00 2001 From: Michele Facchinelli Date: Fri, 2 Feb 2024 14:19:17 +0000 Subject: [PATCH 17/21] Remove unnecessary `Sealed` attributes on classes --- src/data/+mag/+hk/Power.m | 2 +- src/data/+mag/+hk/Processor.m | 2 +- src/data/+mag/+hk/SID15.m | 2 +- src/data/+mag/+hk/Status.m | 2 +- src/data/+mag/Science.m | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data/+mag/+hk/Power.m b/src/data/+mag/+hk/Power.m index 12b6a8d9..1f6af0d7 100644 --- a/src/data/+mag/+hk/Power.m +++ b/src/data/+mag/+hk/Power.m @@ -1,4 +1,4 @@ -classdef (Sealed) Power < mag.HK +classdef Power < mag.HK % POWER Class containing MAG power HK packet data. properties (Dependent) diff --git a/src/data/+mag/+hk/Processor.m b/src/data/+mag/+hk/Processor.m index feb39aea..c1caf4f2 100644 --- a/src/data/+mag/+hk/Processor.m +++ b/src/data/+mag/+hk/Processor.m @@ -1,4 +1,4 @@ -classdef (Sealed) Processor < mag.HK +classdef Processor < mag.HK % PROCESSOR Class containing MAG processor HK packet data. properties (Dependent) diff --git a/src/data/+mag/+hk/SID15.m b/src/data/+mag/+hk/SID15.m index ecb43b80..2e04faac 100644 --- a/src/data/+mag/+hk/SID15.m +++ b/src/data/+mag/+hk/SID15.m @@ -1,4 +1,4 @@ -classdef (Sealed) SID15 < mag.HK +classdef SID15 < mag.HK % SID15 Class containing MAG SID15 HK packet data. properties (Dependent) diff --git a/src/data/+mag/+hk/Status.m b/src/data/+mag/+hk/Status.m index 46081d2d..80b7b75c 100644 --- a/src/data/+mag/+hk/Status.m +++ b/src/data/+mag/+hk/Status.m @@ -1,4 +1,4 @@ -classdef (Sealed) Status < mag.HK +classdef Status < mag.HK % STATUS Class containing MAG status HK packet data. properties (Dependent) diff --git a/src/data/+mag/Science.m b/src/data/+mag/Science.m index 12fb4aaf..98ab2cea 100644 --- a/src/data/+mag/Science.m +++ b/src/data/+mag/Science.m @@ -1,4 +1,4 @@ -classdef (Sealed) Science < mag.TimeSeries & matlab.mixin.CustomDisplay +classdef Science < mag.TimeSeries & matlab.mixin.CustomDisplay % SCIENCE Class containing MAG science data. properties (Dependent) From 6cd2bb8b67b8159fc9a634eea5b0fdeeeb5bfa0f Mon Sep 17 00:00:00 2001 From: Michele Facchinelli Date: Fri, 2 Feb 2024 14:32:53 +0000 Subject: [PATCH 18/21] Add tests for `mag.Instrument` --- resources/ReleaseNotes.md | 3 +- tests/unit/data/tInstrument.m | 106 ++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 tests/unit/data/tInstrument.m diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index ea41e267..97091903 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -12,9 +12,10 @@ - Reintroduce filtering for charts - Rename `cropDataBasedOnScience` to `cropToMatch` - Make sure derivative is empty when data is empty +- Remove unnecessary `Sealed` attributes on classes - Fix issue with consecutive events of the same type missing a completion message - Fix issue when cropping data and no timestamps are selected - Fix issue with `mag.graphics.chart.Stem` not showing markers -- Add tests for `mag.PSD` and `mag.Science/computePSD` +- Add tests for `mag.Instrument`, `mag.PSD` and `mag.Science/computePSD` - Add tests for `mag.graphics.mixin.mustBeColor` - Fix typo in `MarkerSupportTestCase` \ No newline at end of file diff --git a/tests/unit/data/tInstrument.m b/tests/unit/data/tInstrument.m new file mode 100644 index 00000000..d91705d6 --- /dev/null +++ b/tests/unit/data/tInstrument.m @@ -0,0 +1,106 @@ +classdef tInstrument < matlab.mock.TestCase +% TINSTRUMENT Unit tests for "mag.Instrument" class. + + properties (TestParameter) + HasProperty = {"HasData", "HasMetaData", "HasScience", "HasHK"} + end + + methods (Test) + + % Test that "Has*" properties return "false" when object has no + % data. + function hasProperties_noData(testCase, HasProperty) + + % Set up. + instrument = mag.Instrument(); + + % Exercise and verify. + testCase.verifyFalse(instrument.(HasProperty), """" + HasProperty + """ should return ""false"" when object has no data."); + end + + % Test that "TimeRange" is missing when object has no data. + function timeRange_noData(testCase) + + % Set up. + instrument = mag.Instrument(); + + % Exercise and verify. + testCase.verifyTrue(all(ismissing(instrument.TimeRange)), """TimeRange"" should return ""missing"" when object has no data."); + end + + % Test that "TimeRange" is based on both primary and secondary + % data. + function timeRange_withData(testCase) + + % % Set up. + % [instrument, primaryBehavior, secondaryBehavior] = testCase.createTestData(); + % + % minTime = datetime("yesterday"); + % maxTime = datetime("tomorrow"); + % + % testCase.assignOutputsWhen(get(primaryBehavior.Time), [minTime; datetime("today")]); + % testCase.assignOutputsWhen(get(secondaryBehavior.Time), [datetime("today"); maxTime]); + % + % expectedTimeRange = [minTime, maxTime]; + % + % % Exercise and verify. + % testCase.verifyEqual(instrument.TimeRange, expectedTimeRange, """TimeRange"" should return minimum and maximum time based on both sensors."); + end + + % Test that primary sensor name is returned correctly. + function getSensor_primary(testCase) + + % Set up. + instrument = mag.Instrument(MetaData = mag.meta.Instrument()); + instrument.MetaData.Primary = "FIB"; + + % Exercise and verify. + testCase.verifyEqual(instrument.getSensor(), mag.meta.Sensor.FIB, "Primary sensor should be returned by default."); + testCase.verifyEqual(instrument.getSensor("Primary"), mag.meta.Sensor.FIB, "Primary sensor should be returned when asked."); + end + + % Test that secondary sensor name is returned correctly. + function getSensor_secondary(testCase) + + % Set up. + instrument = mag.Instrument(MetaData = mag.meta.Instrument()); + instrument.MetaData.Primary = "FIB"; + + % Exercise and verify. + testCase.verifyEqual(instrument.getSensor("Secondary"), mag.meta.Sensor.FOB, "Secondary sensor should be returned when asked."); + end + + % Test that "copy" method performs a deep copy of all data. + function copyMethod(testCase) + + % Set up. + instrument = testCase.createTestData(); + + % Exercise. + copiedInstrument = instrument.copy(); + + % Verify. + testCase.verifyNotSameHandle(instrument, copiedInstrument, "Copied data should be different instance."); + testCase.verifyNotSameHandle(instrument.MetaData, copiedInstrument.MetaData, "Copied data should be different instance."); + testCase.verifyNotSameHandle(instrument.Primary, copiedInstrument.Primary, "Copied data should be different instance."); + testCase.verifyNotSameHandle(instrument.Secondary, copiedInstrument.Secondary, "Copied data should be different instance."); + testCase.verifyNotSameHandle(instrument.HK, copiedInstrument.HK, "Copied data should be different instance."); + end + end + + methods (Access = private) + + function [instrument, primaryBehavior, secondaryBehavior, hkBehavior] = createTestData(testCase) + + [primary, primaryBehavior] = testCase.createMock(?mag.Science, ConstructorInputs = {timetable.empty(), mag.meta.Science()}, Strict = true); + [secondary, secondaryBehavior] = testCase.createMock(?mag.Science, ConstructorInputs = {timetable.empty(), mag.meta.Science()}, Strict = true); + + [hk, hkBehavior] = testCase.createMock(?mag.HK, ConstructorInputs = {timetable.empty(), mag.meta.HK()}); + + instrument = mag.Instrument(MetaData = mag.meta.Instrument(), ... + Primary = primary, ... + Secondary = secondary, ... + HK = hk); + end + end +end From 08dbb0b1ca42293164eb9f9050ea652890d5c90b Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Mon, 5 Feb 2024 17:16:49 +0000 Subject: [PATCH 19/21] Fix typo --- src/data/+mag/Instrument.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/+mag/Instrument.m b/src/data/+mag/Instrument.m index 17285f50..a1e1b9f0 100644 --- a/src/data/+mag/Instrument.m +++ b/src/data/+mag/Instrument.m @@ -141,7 +141,7 @@ function cropToMatch(this, startTime, endTime) arguments this (1, 1) mag.Instrument startTime (1, 1) datetime = this.TimeRange(1) - endTime (1, 1) datetile = this.TimeRange(2) + endTime (1, 1) datetime = this.TimeRange(2) end % Filter events. From c26dac1eae8b0ade618f7907229c342a153b925f Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Mon, 5 Feb 2024 17:17:51 +0000 Subject: [PATCH 20/21] Separate calculation of spectrogram in separate function --- resources/ReleaseNotes.md | 1 + src/utility/+mag/computeSpectrogram.m | 59 +++++++++++++++++++ .../+mag/+graphics/+chart/Spectrogram.m | 40 +------------ 3 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 src/utility/+mag/computeSpectrogram.m diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index 97091903..cbd6ac07 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -7,6 +7,7 @@ - Add method to `mag.Instrument` to fill warm-up with `missing` data - Add method to `mag.Science` to replace periods with a filler variable +- Separate calculation of spectrogram in separate function - Replace last element of file with `missing` to improve plot where data is missing - Science object with no data is considered empty in `mag.Instrument` - Reintroduce filtering for charts diff --git a/src/utility/+mag/computeSpectrogram.m b/src/utility/+mag/computeSpectrogram.m new file mode 100644 index 00000000..145ca850 --- /dev/null +++ b/src/utility/+mag/computeSpectrogram.m @@ -0,0 +1,59 @@ +function [f, t, p] = computeSpectrogram(x, y, options) +% COMPUTESPECTROGRAM Calculate spectrogram for given signal, as a function +% of time and frequency. + + arguments (Input) + x (:, 1) datetime + y (:, 1) double + options.FrequencyLimits (1, 2) double = [missing(), missing()] + options.FrequencyPoints (1, 1) double = 256 + options.Overlap (1, 1) double {mustBeGreaterThan(options.Overlap, 0), mustBeLessThan(options.Overlap, 1)} = 0.8 + options.Normalize (1, 1) logical = true + options.Window (1, 1) double = missing() + end + + arguments (Output) + f (:, 1) double + t (:, 1) double + p (:, :) double + end + + % Normalize data. + if options.Normalize + + if height(y) < 500 + y = normalize(y); + else + + k = ceil(height(y) / 100); + y = (y - movmean(y, k)) ./ movstd(y, k); + end + end + + % Compute spectrogram coefficients. + rate = round(1 / mode(seconds(diff(x)))); + + if ~ismissing(options.Window) + w = options.Window; + elseif rate > 100 + w = 25; + else + w = 5; + end + + window = rate * w; + overlap = window * options.Overlap; + + % Spectrogram. + y(ismissing(y) | isinf(y)) = 0; + + [~, f, t, p] = spectrogram(y, window, overlap, 2 * options.FrequencyPoints, rate); + + % Filter frequencies outside bands. + if ~any(ismissing(options.FrequencyLimits)) + + locF = (f >= options.FrequencyLimits(1)) & (f <= options.FrequencyLimits(2)); + f = f(locF); + p = p(locF, :); + end +end \ No newline at end of file diff --git a/src/visualize/+mag/+graphics/+chart/Spectrogram.m b/src/visualize/+mag/+graphics/+chart/Spectrogram.m index 63006034..e4b69905 100644 --- a/src/visualize/+mag/+graphics/+chart/Spectrogram.m +++ b/src/visualize/+mag/+graphics/+chart/Spectrogram.m @@ -6,9 +6,9 @@ % spikes. Normalize (1, 1) logical = true % FREQUENCYLIMITS Specifies the frequency band limits. - FrequencyLimits (1, 2) double = NaN(1, 2) + FrequencyLimits (1, 2) double = [missing(), missing()] % WINDOW Length of window. - Window (1, 1) double = NaN() + Window (1, 1) double = missing() end methods @@ -38,42 +38,8 @@ xData = this.getXData(data); yData = this.getYData(data); - % Normalize data. - if this.Normalize - - if height(yData) < 500 - yData = (yData - mean(yData)) ./ std(yData); - else - - k = ceil(height(yData) / 100); - yData = (yData - movmean(yData, k)) ./ movstd(yData, k); - end - end - - % Compute spectrogram coefficients. - rate = round(1 / mode(seconds(diff(xData)))); - o = 0.8; - - if ~ismissing(this.Window) - w = this.Window; - elseif rate > 100 - w = 25; - else - w = 5; - end - % Spectrogram. - yData(ismissing(yData) | isinf(yData)) = 0; - - [~, f, t, p] = spectrogram(yData, rate * w, rate * w * o, 512, rate); - - % Filter frequencies outside bands. - if ~any(ismissing(this.FrequencyLimits)) - - locF = (f >= this.FrequencyLimits(1)) & (f <= this.FrequencyLimits(2)); - f = f(locF); - p = p(locF, :); - end + [f, t, p] = mag.computeSpectrogram(xData, yData, FrequencyLimits = this.FrequencyLimits, Normalize = this.Normalize, Window = this.Window); % Plot. graph = surf(axes, xData(1) + seconds(t), f, pow2db(abs(p)), EdgeColor = "none"); From 40f88775633b04ff8d18cad70de9649d129717d2 Mon Sep 17 00:00:00 2001 From: mfacchinelli Date: Mon, 5 Feb 2024 17:18:12 +0000 Subject: [PATCH 21/21] Add save/load utility class --- resources/ReleaseNotes.md | 1 + .../IMAPTestingAnalysis.m | 13 +++++------ src/utility/+mag/+mixin/SaveLoad.m | 22 +++++++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 src/utility/+mag/+mixin/SaveLoad.m diff --git a/resources/ReleaseNotes.md b/resources/ReleaseNotes.md index cbd6ac07..4ff5d5f0 100644 --- a/resources/ReleaseNotes.md +++ b/resources/ReleaseNotes.md @@ -7,6 +7,7 @@ - Add method to `mag.Instrument` to fill warm-up with `missing` data - Add method to `mag.Science` to replace periods with a filler variable +- Add save/load utility class - Separate calculation of spectrogram in separate function - Replace last element of file with `missing` to improve plot where data is missing - Science object with no data is considered empty in `mag.Instrument` diff --git a/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m b/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m index aae83afd..7083c097 100644 --- a/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m +++ b/src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m @@ -1,9 +1,8 @@ -classdef (Sealed) IMAPTestingAnalysis < matlab.mixin.Copyable & mag.mixin.SetGet +classdef (Sealed) IMAPTestingAnalysis < matlab.mixin.Copyable & mag.mixin.SetGet & mag.mixin.SaveLoad % IMAPTESTINGANALYSIS Automate analysis of an IMAP CPT or SFT folder. - properties (Constant, Hidden) - % VERSION Version number. - Version (1, 1) string = mag.version() + properties (Constant) + Version = mag.version() end properties @@ -418,7 +417,7 @@ function load(this) elseif isa(object, "struct") % Recreate object based on version. - if isfield(object, "Outboard") + if isequal(object.OriginalVersion, 1.0) % Convert object to version 2.0 and recursively % dispatch it. @@ -441,9 +440,9 @@ function load(this) end loadedObject = mag.IMAPTestingAnalysis.loadobj(loadedObject); - elseif isfield(object, "PrimaryRamp") + elseif isequal(object.OriginalVersion, 2.0) - % Convert object directly to version 3.0. + % Convert object directly to version 2.5. loadedObject = mag.IMAPTestingAnalysis(); loadedObject.Results = mag.Instrument(); diff --git a/src/utility/+mag/+mixin/SaveLoad.m b/src/utility/+mag/+mixin/SaveLoad.m new file mode 100644 index 00000000..0863d652 --- /dev/null +++ b/src/utility/+mag/+mixin/SaveLoad.m @@ -0,0 +1,22 @@ +classdef (Abstract, HandleCompatible) SaveLoad +% SAVELOAD Utility class to aid saving and loading to MAT files. + + properties (Abstract, Constant) + % VERSION Version number. + Version (1, 1) string + end + + properties (GetAccess = protected, SetAccess = private) + % ORIGINALVERSION Original version for save/load compatibility. + OriginalVersion (1, 1) string + end + + methods (Hidden) + + function savedObject = saveobj(this) + + savedObject = this; + savedObject.OriginalVersion = this.Version; + end + end +end \ No newline at end of file