Skip to content

Commit

Permalink
Merge pull request #797 from aodn/FeatQcFlag
Browse files Browse the repository at this point in the history
(Feat) Add failed QC tests information into new ancillary variable [*]_failed_tests
  • Loading branch information
ggalibert authored Jul 24, 2023
2 parents 1b30843 + 5192361 commit 6f0c125
Show file tree
Hide file tree
Showing 8 changed files with 433 additions and 5 deletions.
118 changes: 116 additions & 2 deletions AutomaticQC/qcFilterMain.m
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@
goodIdx = canBeFlagGoodIdx & goodIdx;
probGoodIdx = canBeFlagProbGoodIdx & probGoodIdx;
probBadIdx = canBeFlagProbBadIdx & probBadIdx;
badIdx = canBeFlagBadIdx & badIdx;
badIdx = canBeFlagBadIdx & badIdx;
end

% call the sub fuction to add the results of the failed tests
% into a new variable
addFailedTestsFlags()

sam.(type{m}){k}.flags(goodIdx) = fsam.(type{m}){k}.flags(goodIdx);
sam.(type{m}){k}.flags(probGoodIdx) = fsam.(type{m}){k}.flags(probGoodIdx);
sam.(type{m}){k}.flags(probBadIdx) = fsam.(type{m}){k}.flags(probBadIdx);
Expand Down Expand Up @@ -206,6 +210,10 @@
probBadIdx = f == probBadFlag;
badIdx = f == badFlag;

% call the sub fuction to add the results of the failed tests
% into a new variable
addFailedTestsFlags()

% update new flags in current variable
goodIdx = canBeFlagGoodIdx & goodIdx;
probGoodIdx = canBeFlagProbGoodIdx & probGoodIdx;
Expand Down Expand Up @@ -268,4 +276,110 @@
end
end
end
end

function addFailedTestsFlags()
% addFailedTestsFlags inherits all the variable available in the
% workspace.
%
% we now have for the test filterName the result (good, probgood, probBad, bad) value for each variable and each datapoint.
% For most QC routines, a failed test is where data is not flagged
% as good.
% However, more information is available on
% https://github.com/aodn/imos-toolbox/wiki/QCProcedures
%
% This function aims to store, in a new variable named "failed_tests"
% the result of why a data point wasn't flagged as good, i.e. which
% QC routine was responsible for "rejecting" a data point.
%
% To do so, each failed QC routine is linked to the power of 2 of
% an integer.
% For example:
% manualQC is linked to 2^1
% imosCorrMagVelocitySetQC is linked to 2^2
% ...
% Any integer can be written as the sum of disting powers of 2
% numbers (see various proofs online
% https://math.stackexchange.com/questions/176678/strong-induction-proof-every-natural-number-sum-of-distinct-powers-of-2)
%
%
% Note that QC tests can be rerun by the user multiple times. This
% however should be dealt in the FlowManager/displayManager.m
% function
%
%
% the failed tests depends on the test. It is not always the same
% for all. See table for each individual test at
% https://github.com/aodn/imos-toolbox/wiki/QCProcedures
if strcmp(filterName, 'imosTiltVelocitySetQC') % see https://github.com/aodn/imos-toolbox/wiki/QCProcedures#adcp-tilt-test---imostiltvelocitysetqc---compulsory
% For the imosTiltVelocitySetQC, users would like to know if the
% failed test is because the data was flagged as bad or as probably
% good
% this test (imosTiltVelocitySetQC)
% there is a two level flag for failed tests. We're taking this
% into account below
failedTestsIdx_probGood = probGoodIdx;
failedTestsIdx_bad = badIdx;
elseif strcmp(filterName, 'imosErrorVelocitySetQC') % see https://github.com/aodn/imos-toolbox/wiki/QCProcedures#adcp-error-velocity-test---imoserrorvelocitysetqc---optional
% for this specific QC routine, a test is considerer to be pass
% if the data is flagged good or probably good if ECUR is NaN

% find variable index for ECUR
notdone = true;
while notdone
for kk = 1:length(sam.(type{m}))
if strcmp(sam.(type{m}){kk}.name, 'ECUR')
notdone = false;
break
end
end
end

if not(notdone)
if all(isnan(sam.(type{m}){kk}.data(:)))
failedTestsIdx = badIdx | probBadIdx;
else
failedTestsIdx = badIdx | probBadIdx | probGoodIdx ;
end
end

else
failedTestsIdx = probGoodIdx | probBadIdx | badIdx; % result matrice for all variables
end


if strcmp(filterName, 'imosTiltVelocitySetQC')
failedTests_probGood = zeros(size(failedTestsIdx_probGood));
failedTests_probGood(logical(failedTestsIdx_probGood)) = imosQCTest(strcat(filterName, '_probably_good'));
failedTests_probGood = int32(failedTests_probGood);

failedTests_bad = zeros(size(failedTestsIdx_bad));
failedTests_bad(logical(failedTestsIdx_bad)) = imosQCTest(strcat(filterName, '_bad'));
failedTests_bad = int32(failedTests_bad);

if ~isfield(sam.(type{m}){k}, 'failed_tests') % if the flat_tests field does not exist yet
sam.(type{m}){k}.failed_tests = failedTests_bad + failedTests_probGood;

else
sam.(type{m}){k}.failed_tests = sam.(type{m}){k}.failed_tests + failedTests_bad + failedTests_probGood;
end
else

failedTests = zeros(size(failedTestsIdx));
if strcmp(filterName, 'imosHistoricalManualSetQC')
failedTests(logical(failedTestsIdx)) = imosQCTest('userManualQC'); % we replace imosHistoricalManualSetQC with userManualQC so that we only have 1 flag_value/flag_meaning combo in the NetCDF for something very similar in the end
else
failedTests(logical(failedTestsIdx)) = imosQCTest(filterName);
end
failedTests = int32(failedTests);

if ~isfield(sam.(type{m}){k},'failed_tests') % if the flat_tests field does not exist yet
sam.(type{m}){k}.failed_tests = failedTests;
else
% if it already exists, we sum the previous array with the new one
sam.(type{m}){k}.failed_tests = sam.(type{m}){k}.failed_tests + failedTests;


end
end
end
end
16 changes: 14 additions & 2 deletions FlowManager/displayManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -623,8 +623,20 @@ function resetPropQCCallback()

for j=1:length(resetIdx)
for i=1:length(sample_data{resetIdx(j)}.variables)
if isfield(sample_data{resetIdx(j)}.variables(i), 'ancillary_comment')
sample_data{resetIdx(j)}.variables(i) = rmfield(sample_data{resetIdx(j)}.variables(i), 'ancillary_comment');
% previous code could not run since it was using variables(i) instead of variables{i}.
% TODO ask @ggalibert about this
if isfield(sample_data{resetIdx(j)}.variables{i}, 'ancillary_comment')
sample_data{resetIdx(j)}.variables{i} = rmfield(sample_data{resetIdx(j)}.variables{i}, 'ancillary_comment');
end

% delete the failed_tests previous results so that we don't
% sum up again the failed QC routines. The good thing is
% that all QC routines a reset anyway, which makes our
% life easier
if isfield(sample_data{resetIdx(j)}.variables{i}, 'failed_tests')
% we don't bother doing heavy calculation.
% We simply delete the "failed_tests" variable
sample_data{resetIdx(j)}.variables{i} = rmfield(sample_data{resetIdx(j)}.variables{i}, 'failed_tests');
end
end

Expand Down
9 changes: 9 additions & 0 deletions FlowManager/flowManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,15 @@ function manualQCRequestCallback(setIdx, varIdx, dataIdx, flag, manualQcComment)
% we update the flags values
autoQCData{setIdx}.variables{varIdx}.flags(dataIdx) = flag;

% update the failed_tests field containing information on failed tests
% with manual QC information
if isfield(autoQCData{setIdx}.variables{varIdx}, 'failed_tests')
autoQCData{setIdx}.variables{varIdx}.failed_tests(dataIdx) = imosQCTest('userManualQC'); % if manual QC done on a point, we overwrite previous failed QC routine information
else
autoQCData{setIdx}.variables{varIdx}.failed_tests(dataIdx) = zeros(size(dataIdx)); % initialise array
autoQCData{setIdx}.variables{varIdx}.failed_tests(dataIdx) = imosQCTest('userManualQC');
end

qcSet = str2double(readProperty('toolbox.qc_set'));
rawFlag = imosQCFlag('raw', qcSet, 'flag');

Expand Down
57 changes: 57 additions & 0 deletions IMOS/imosQCTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
function value = imosQCTest( testName )
%imosQCTest Returns an appropriate QC flag_value (integer)
% given a qc test routine (String)

% The value returned by this function is the power of 2 of the qc routine
% positional integer available in imosQCTests.txt. This is used to store
% information in a variable in the form of integers
%
%
% Inputs:
%
% testName - name of QC test
%
% Outputs:
% value - integer
%
% Author: Laurent Besnard <[email protected]>
%
% Example:
%
% value = imosQCTest( 'userManualQC');
%
%
% assert(value==2)
%

narginchk(1, 1);
if ~ischar(testName), error('field must be a string'); end

value = '';

% open the IMOSQCFTests.txt file - it should be
% in the same directory as this m-file
path = '';
if ~isdeployed, [path, ~, ~] = fileparts(which('imosToolbox.m')); end
if isempty(path), path = pwd; end
path = fullfile(path, 'IMOS');

fidS = -1;
try
% read in the QC sets
fidS = fopen([path filesep 'imosQCTests.txt'], 'rt');
if fidS == -1, return; end
sets = textscan(fidS, '%s%f', 'delimiter', ',', 'commentStyle', '%');
fclose(fidS);

[~, idx] = ismember(testName, sets{1,1});

value = int32(2^sets{1,2}(idx)); % return the


catch e
if fidS ~= -1, fclose(fidS); end
rethrow(e);
end


64 changes: 64 additions & 0 deletions IMOS/imosQCTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
function values = imosQCTests()
%imosQCTests Returns a 1x2 cell array reading the info found in
%ImosQCTests.txt
% - the first one contains a list of QC routines
% - the second cell returns a positional integer for each routine
%
% Inputs:
%
% N.A.
%
% Outputs:
% 1x2 cell array
%
% Author: Laurent Besnard <[email protected]>
%

%
% Copyright (C) 2023, Australian Ocean Data Network (AODN) and Integrated
% Marine Observing System (IMOS).
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation version 3 of the License.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.

% You should have received a copy of the GNU General Public License
% along with this program.
% If not, see <https://www.gnu.org/licenses/gpl-3.0.en.html>.
%

% open the IMOSQCFTests file - it should be
% in the same directory as this m-file
path = '';
if ~isdeployed, [path, ~, ~] = fileparts(which('imosToolbox.m')); end
if isempty(path), path = pwd; end
path = fullfile(path, 'IMOS');

fidS = -1;
try
% read in the QC sets
fidS = fopen([path filesep 'imosQCTests.txt'], 'rt');
if fidS == -1, return; end
sets = textscan(fidS, '%s%f', 'delimiter', ',', 'commentStyle', '%');
fclose(fidS);

% rewrite sets with correct qc flag test values using the imosQCTest
% function
nQcFlags = length(sets{1});
for k = 1:nQcFlags
sets{1,2}(k) = imosQCTest(sets{1,1}{k});
end

values = sets;

catch e
if fidS ~= -1, fclose(fidS); end
rethrow(e);
end


35 changes: 35 additions & 0 deletions IMOS/imosQCTests.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
%
% A list of all the toolbox QC routines flag values. This list is intended
% to by used by the qcFilterMain.m function to tag, for each data point,
% which QC test failed
%
% the positional integer value has to be strictly above 0 since 0 is used
% as a _FillValue
%
% QC routine, positional integer
userManualQC, 0
imosCorrMagVelocitySetQC, 1
imosDensityInversionSetQC, 2
imosEchoIntensitySetQC, 3
imosEchoIntensityVelocitySetQC, 4
imosEchoRangeSetQC, 5
imosErrorVelocitySetQC, 6
imosGlobalRangeQC, 7
imosHorizontalVelocitySetQC, 8
imosImpossibleDateQC, 10
imosImpossibleDepthQC, 11
imosImpossibleLocationSetQC, 12
imosInOutWaterQC, 13
imosPercentGoodVelocitySetQC, 14
imosRateOfChangeQC, 15
imosRegionalRangeQC, 16
imosSalinityFromPTQC, 17
imosSideLobeVelocitySetQC, 18
imosStationarityQC, 19
imosSurfaceDetectionByDepthSetQC, 20
imosTier2ProfileVelocitySetQC, 21
imosTiltVelocitySetQC_probably_good,22
imosTiltVelocitySetQC_bad, 23
imosTimeSeriesSpikeQC, 24
imosVerticalSpikeQC, 25
imosVerticalVelocityQC, 26
Loading

0 comments on commit 6f0c125

Please sign in to comment.