Skip to content

Commit

Permalink
Merge pull request #33 from ImperialCollegeLondon/vNext
Browse files Browse the repository at this point in the history
 Update software to v3.5.0
  • Loading branch information
mfacchinelli authored Mar 18, 2024
2 parents c38561c + 72e7dae commit 8117b54
Show file tree
Hide file tree
Showing 19 changed files with 207 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/matlab.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
if: github.ref == 'refs/heads/main'
needs: test
env:
VERSION: "3.4.1"
VERSION: "3.5.0"
steps:
- name: Check out repository
uses: actions/checkout@v3
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

[![MATLAB Tests](https://github.com/ImperialCollegeLondon/MAG-Data-Visualization-Toolbox/actions/workflows/matlab.yml/badge.svg)](https://github.com/ImperialCollegeLondon/MAG-Data-Visualization-Toolbox/actions/workflows/matlab.yml)

This repository contains utilities for processing and visualizing MAG science and HK data. The supported MATLAB releases are MATLAB R2023b and later.
This repository contains utilities for processing and visualizing MAG science and HK data. The supported MATLAB releases are MATLAB R2023b and later. The following MATLAB toolboxes are required to use the toolbox:

* MATLAB
* Signal Processing Toolbox
* Statistics and Machine Learning Toolbox
* Text Analytics Toolbox

## Getting Started

Expand Down
Binary file modified app/DataVisualization.mlapp
Binary file not shown.
14 changes: 6 additions & 8 deletions resources/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
# App

- Make sure to reset everything when "Reset" button is pressed

# Software

- Fix issue with exporting empty HK
- Fix issue with plotting CPT data with no mode cycling information
- Fix issue with `tiledlayout` spacing when tile indexing is column-major
- Fix issue with naming HK close up figure
- Make dependence on Parallel Computing Toolbox optional
- Convert addition of missing row at end of file data into a processing step (`mag.process.Separate`)
- Add `mag.process.Separate` to HK processing
- Add export of whole science data
- Fix issue with processing data ready time in `mag.process.Units` when not enough digits are present
- Fix issue with plotting last event mode and range in `mag.graphics.view.HK` when data is cropped
73 changes: 73 additions & 0 deletions src/analyze/+mag/+process/Separate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
classdef Separate < mag.process.Step
% SEPARATE Add row with missing data at end of tabular to separate
% different files. Avoid continuous lines when gap between files is large.

properties (Dependent)
Name
Description
DetailedDescription
end

properties
% DISCRIMINATIONVARIABLE Name of variable to increase in row.
DiscriminationVariable (1, 1) string
% VARIABLES Variables to be set to missing.
Variables (1, :) string
end

methods

function this = Separate(options)

arguments
options.?mag.process.Separate
end

this.assignProperties(options);
end

function value = get.Name(~)
value = "Add Missing Row to Separate Files";
end

function value = get.Description(this)
value = "Add extra row with missing values for " + join(compose("""%s""", this.Variables), ", ") + ".";
end

function value = get.DetailedDescription(this)
value = this.Description + " This is to avoid continuous lines when gap between files is large.";
end

function data = apply(this, data, ~)

arguments
this (1, 1) mag.process.Separate
data tabular
~
end

if isequal(this.Variables, "*")

locMissingCompatible = varfun(@this.isMissingCompatible, data, OutputFormat = "uniform");
variables = data.Properties.VariableNames(locMissingCompatible);

variables(variables == this.DiscriminationVariable) = [];
else
variables = this.Variables;
end

finalRow = data(end, :);
finalRow.(this.DiscriminationVariable) = finalRow.(this.DiscriminationVariable) + eps();
finalRow{:, variables} = missing();

data = [data; finalRow];
end
end

methods (Static, Access = private)

function tf = isMissingCompatible(x)
tf = isa(x, "single") | isa(x, "double") | isa(x, "duration") | isa(x, "calendarDuration") | isa(x, "datetime") | isa(x, "categorical") | isa(x, "string");
end
end
end
4 changes: 4 additions & 0 deletions src/analyze/+mag/+process/Units.m
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@

binRT = dec2bin(readyTime);

if width(binRT) < 24
binRT = char(pad(string(binRT), 25, "left", "0"));
end

fineTime = bin2dec(binRT(:, end-23:end));
fineTime = fineTime / (2^24-1);

Expand Down
6 changes: 4 additions & 2 deletions src/analyze/+mag/@IMAPTestingAnalysis/IMAPTestingAnalysis.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
mag.process.AllZero(Variables = ["coarse", "fine", "x", "y", "z"]), ...
mag.process.Timestamp(), ...
mag.process.DateTime(), ...
mag.process.SignedInteger(CompressionVariable = "compression", Variables = ["x", "y", "z"])]
mag.process.SignedInteger(CompressionVariable = "compression", Variables = ["x", "y", "z"]), ...
mag.process.Separate(DiscriminationVariable = "t", Variables = ["x", "y", "z"])]
% WHOLEDATAPROCESSING Steps needed to process all of imported data.
WholeDataProcessing (1, :) mag.process.Step = [ ...
mag.process.Sort(), ...
Expand All @@ -52,7 +53,8 @@
% HKPROCESSING Steps needed to process imported HK data.
HKProcessing (1, :) mag.process.Step = [ ...
mag.process.DateTime(), ...
mag.process.Units()]
mag.process.Units(), ...
mag.process.Separate(DiscriminationVariable = "t", Variables = "*")]
end

properties (Dependent)
Expand Down
16 changes: 14 additions & 2 deletions src/analyze/+mag/@IMAPTestingAnalysis/export.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,28 @@ function export(this, exportStrategy, options)
scienceExportFormat = exportStrategy.ScienceExportFormat;
hkExportFormat = exportStrategy.HKExportFormat;

% Export full science.
if this.Results.HasScience

r = this.Results.copy();
r.crop(period);

scienceData = scienceExportFormat.formatForExport(r);

exportStrategy.ExportFileName = fullfile(options.Location, compose("%s %s (%d, %d)", datestr(r.Primary.MetaData.Timestamp, "ddmmyy-hhMM"), r.Primary.MetaData.Mode, r.Primary.MetaData.DataFrequency, r.Secondary.MetaData.DataFrequency) + extension); %#ok<DATST>
exportStrategy.export(scienceData);
end

% Export each mode.
modes = this.getAllModes();

for m = modes

m.crop(period);
exportedData = scienceExportFormat.formatForExport(m);
modeData = scienceExportFormat.formatForExport(m);

exportStrategy.ExportFileName = fullfile(options.Location, compose("%s %s (%d, %d)", datestr(m.Primary.MetaData.Timestamp, "ddmmyy-hhMM"), m.Primary.MetaData.Mode, m.Primary.MetaData.DataFrequency, m.Secondary.MetaData.DataFrequency) + extension); %#ok<DATST>
exportStrategy.export(exportedData);
exportStrategy.export(modeData);
end

% Export I-ALiRT.
Expand Down
5 changes: 0 additions & 5 deletions src/analyze/+mag/@IMAPTestingAnalysis/loadIALiRTData.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ function loadIALiRTData(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<AGROW>
Expand Down
5 changes: 0 additions & 5 deletions src/analyze/+mag/@IMAPTestingAnalysis/loadScienceData.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ 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<AGROW>
Expand Down
2 changes: 1 addition & 1 deletion src/data/+mag/+meta/+log/GSEOS.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
end

dataStore = tabularTextDatastore(this.FileName, FileExtensions = this.Extensions, TextType = "string", VariableNames = this.Names, TextscanFormats = this.Formats);
rawData = dataStore.readall(UseParallel = ~isempty(gcp("nocreate")));
rawData = dataStore.readall(UseParallel = mag.internal.useParallel());

if isempty(rawData)
return;
Expand Down
2 changes: 1 addition & 1 deletion src/data/+mag/+meta/+log/SID15.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

% Load data.
dataStore = tabularTextDatastore(this.FileName, TextType = "string", FileExtensions = this.Extensions, SelectedVariableNames = ["SHCOARSE", "ISV_FOB_ACTTRIES", "ISV_FIB_ACTTRIES"]);
rawData = dataStore.readall(UseParallel = ~isempty(gcp("nocreate")));
rawData = dataStore.readall(UseParallel = mag.internal.useParallel());

rawData = sortrows(rawData, "SHCOARSE");

Expand Down
3 changes: 1 addition & 2 deletions src/io/+mag/+io/CSV.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@

% Import data as tables.
data = cell.empty();
useParallel = ~isempty(gcp("nocreate"));

for i = 1:numel(this.ImportFileNames)

Expand All @@ -46,7 +45,7 @@
end

dataStore = tabularTextDatastore(this.ImportFileNames(i), FileExtensions = this.Extension);
data{i} = dataStore.readall(UseParallel = useParallel);
data{i} = dataStore.readall(UseParallel = mag.internal.useParallel());
end
end

Expand Down
6 changes: 6 additions & 0 deletions src/utility/+mag/+internal/useParallel.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function tf = useParallel()
% USEPARALLEL Determine whether to use Parallel Computing Toolbox to
% process data. Does not require Parallel Computing Toolbox to run.

tf = license("test", "Distrib_Computing_Toolbox") && ~isempty(gcp("nocreate"));
end
2 changes: 1 addition & 1 deletion src/visualize/+mag/+graphics/+chart/+custom/Event.m
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
plotColors = this.getColors(variable);

for i = 1:numel(variable)
graph(i) = plot(axes, plotTime(:, i), plotVariable(:, i), Color = plotColors(i, :), LineWidth = 3.5); %#ok<AGROW>
graph(i) = plot(axes, plotTime(:, i), plotVariable(:, i), Color = plotColors(i, :), LineWidth = 3.5);
end

% Plot vertical lines between mode changes.
Expand Down
8 changes: 4 additions & 4 deletions src/visualize/+mag/+graphics/+view/HK.m
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ function visualize(this)
mag.graphics.style.LeftRight(Title = "1.8 V", LeftLabel = "[V]", RightLabel = "[mA]", LLimits = 1.8 + this.VOffset, Charts = [mag.graphics.chart.Plot(YVariables = "P1V8V"), mag.graphics.chart.Plot(YVariables = "P1V8I")]), ...
mag.graphics.style.LeftRight(Title = "3.3 V", LeftLabel = "[V]", RightLabel = "[mA]", LLimits = 3.3 + this.VOffset, Charts = [mag.graphics.chart.Plot(YVariables = "P3V3V"), mag.graphics.chart.Plot(YVariables = "P3V3I")]), ...
mag.graphics .style.Default(Title = "2.4 V", YLabel = "[V]", YAxisLocation = "right", Charts = mag.graphics.chart.Plot(YVariables = "P2V4V"))], ...
primary.Events, mag.graphics.style.Default(Title = compose("%s Modes", primarySensor), YLabel = "mode [-]", YLimits = "manual", Charts = mag.graphics.chart.custom.Event(EventOfInterest = "DataFrequency")), ...
secondary.Events, mag.graphics.style.Default(Title = compose("%s Modes", secondarySensor), YLabel = "mode [-]", YLimits = "manual", YAxisLocation = "right", Charts = mag.graphics.chart.custom.Event(EventOfInterest = "DataFrequency")), ...
primary.Events, mag.graphics.style.Default(Title = compose("%s Ranges", primarySensor), YLabel = "range [-]", YLimits = "manual", Charts = mag.graphics.chart.custom.Event(EventOfInterest = "Range", YOffset = 0.1, IgnoreMissing = false)), ...
secondary.Events, mag.graphics.style.Default(Title = compose("%s Ranges", secondarySensor), YLabel = "range [-]", YLimits = "manual", YAxisLocation = "right", Charts = mag.graphics.chart.custom.Event(EventOfInterest = "Range", YOffset = 0.1, IgnoreMissing = false)), ...
primary.Events, mag.graphics.style.Default(Title = compose("%s Modes", primarySensor), YLabel = "mode [-]", YLimits = "manual", Charts = mag.graphics.chart.custom.Event(EventOfInterest = "DataFrequency", EndTime = primary.Time(end))), ...
secondary.Events, mag.graphics.style.Default(Title = compose("%s Modes", secondarySensor), YLabel = "mode [-]", YLimits = "manual", YAxisLocation = "right", Charts = mag.graphics.chart.custom.Event(EventOfInterest = "DataFrequency", EndTime = secondary.Time(end))), ...
primary.Events, mag.graphics.style.Default(Title = compose("%s Ranges", primarySensor), YLabel = "range [-]", YLimits = "manual", Charts = mag.graphics.chart.custom.Event(EventOfInterest = "Range", YOffset = 0.1, IgnoreMissing = false, EndTime = primary.Time(end))), ...
secondary.Events, mag.graphics.style.Default(Title = compose("%s Ranges", secondarySensor), YLabel = "range [-]", YLimits = "manual", YAxisLocation = "right", Charts = mag.graphics.chart.custom.Event(EventOfInterest = "Range", YOffset = 0.1, IgnoreMissing = false, EndTime = secondary.Time(end))), ...
Name = "HK & Events", ...
Arrangement = [4, 2], ...
LinkXAxes = true, ...
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/analyze/MAGAnalysisTestCase.m
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
classdef (Abstract) MAGAnalysisTestCase < matlab.unittest.TestCase
% MAGANALYSISTESTCASE Base class for all MAG analysis tests.

methods (Access = protected)
methods (Static, Access = protected)

function data = createTestData(~, options)
function data = createTestData(options)

arguments
~
options.XYZ (:, 3) double = ones(3, 3)
options.Range (:, 1) double = zeros(3, 1)
options.Sequence (:, 1) double = [1; 2; 3]
Expand Down
61 changes: 61 additions & 0 deletions tests/unit/analyze/tSeparate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
classdef tSeparate < MAGAnalysisTestCase
% TSEPARATE Unit tests for "mag.process.Separate" classes.

methods (Test)

% Test that adding separation row with specific variables only
% applies to those variables.
function namedVariables(testCase)

% Set up.
data = testCase.createTestData();

missingVariables = ["Doubles", "Strings"];
nonMissingVariables = setdiff(data.Properties.VariableNames, missingVariables);

% Exercise.
separateStep = mag.process.Separate(DiscriminationVariable = "Discriminator", Variables = missingVariables);
processedData = separateStep.apply(data);

% Verify.
testCase.assertSize(processedData, [height(data) + 1, width(data)], "Separation row should have been added.");

testCase.verifyTrue(all(ismissing(processedData(end, missingVariables))), "Selected variables should be missing.");
testCase.verifyFalse(any(ismissing(processedData(end, nonMissingVariables))), "Other variables should not be missing.");

testCase.verifyGreaterThanOrEqual(data{end, "Discriminator"}, processedData{end, "Discriminator"}, "Discrimination variable should be increased.");
end

% Test that adding separation row with all variables only
% applies to variables that support .
function allVariables(testCase)

% Set up.
data = testCase.createTestData();

nonMissingVariables = ["Integers", "Logicals", "Discriminator"];
missingVariables = setdiff(data.Properties.VariableNames, nonMissingVariables);

% Exercise.
separateStep = mag.process.Separate(DiscriminationVariable = "Discriminator", Variables = "*");
processedData = separateStep.apply(data);

% Verify.
testCase.assertSize(processedData, [height(data) + 1, width(data)], "Separation row should have been added.");

testCase.verifyTrue(all(ismissing(processedData(end, missingVariables))), "Selected variables should be missing.");
testCase.verifyFalse(any(ismissing(processedData(end, nonMissingVariables))), "Other variables should not be missing.");

testCase.verifyGreaterThanOrEqual(data{end, "Discriminator"}, processedData{end, "Discriminator"}, "Discrimination variable should be increased.");
end
end

methods (Static, Access = protected)

function data = createTestData()

data = table(ones(3, 1), ones(3, 1, "uint32"), minutes([3; 2; 1]), true(3, 1), [datetime("yesterday"); datetime("today"); datetime("now")], ["A"; "B"; "C"], [1; 2; 3], ...
VariableNames = ["Doubles", "Integers", "Durations", "Logicals", "Dates", "Strings", "Discriminator"]);
end
end
end
22 changes: 22 additions & 0 deletions tests/unit/utility/tUseParallel.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
classdef tUseParallel < matlab.unittest.TestCase
% TUSEPARALLEL Unit tests for "mag.internal.useParallel" function.

methods (Test)

% Test that "mag.internal.useParallel" returns "false" when no
% parallel pool is set up.
function noParallelPool(testCase)
testCase.verifyFalse(mag.internal.useParallel(), "Parallel Computing Toolbox should not be used if it is not installed.");
end

% Test that "mag.internal.useParallel" returns "false" when
% Parallel Computing Toolbox is not installed.
function noParallelToolbox(testCase)

license("test", "Distrib_Computing_Toolbox", "disable");
testCase.addTeardown(@() license("test", "Distrib_Computing_Toolbox", "enable"));

testCase.verifyFalse(mag.internal.useParallel(), "Parallel Computing Toolbox should not be used if it is not installed.");
end
end
end

0 comments on commit 8117b54

Please sign in to comment.