Many changes in DBHandler
new general processing structure just before splitting off the projects folder from this repo
This commit is contained in:
@@ -693,6 +693,8 @@ classdef Signal
|
||||
return
|
||||
end
|
||||
|
||||
pkpos = sort(pkpos);
|
||||
|
||||
if mean(w) > 10 || mean(p) > 10
|
||||
return
|
||||
else
|
||||
@@ -709,7 +711,7 @@ classdef Signal
|
||||
|
||||
shifts = lags(pkpos);
|
||||
sequenceStarts = shifts;
|
||||
% shifts = shifts(shifts>=0);
|
||||
shifts = shifts(shifts>=0);
|
||||
|
||||
if numel(shifts) > 0
|
||||
|
||||
@@ -720,17 +722,17 @@ classdef Signal
|
||||
|
||||
for c = shifts
|
||||
sig = obj.delay(-c,'mode','samples');
|
||||
sig.signal = sig.signal(1:length(b)) .* -inverted;
|
||||
sig.signal = sig.signal(1:length(b));% .* -inverted;
|
||||
S{end+1,1} = sig;
|
||||
end
|
||||
|
||||
%return/keep the sinal with the highest correlation (only within positive shifts)
|
||||
[~,idx]=max(pks);
|
||||
obj.signal = S{idx}.signal;
|
||||
%put signal with highest corr. to first index in S array
|
||||
swap = S{1};
|
||||
S{1} = S{idx};
|
||||
S{idx} = swap;
|
||||
% %return/keep the sinal with the highest correlation (only within positive shifts)
|
||||
% [~,idx]=max(pks);
|
||||
% obj.signal = S{idx}.signal;
|
||||
% %put signal with highest corr. to first index in S array
|
||||
% swap = S{1};
|
||||
% S{1} = S{idx};
|
||||
% S{idx} = swap;
|
||||
|
||||
for c = 1:numel(shifts)
|
||||
S{c}.logbook = [];
|
||||
@@ -962,7 +964,7 @@ classdef Signal
|
||||
|
||||
% add information
|
||||
|
||||
if 1
|
||||
if 0
|
||||
|
||||
pwr_dbm = round(obj.power,3);
|
||||
pwr_lin = obj.power("unit",power_notation.W);
|
||||
|
||||
@@ -83,7 +83,7 @@ classdef DBHandler < handle
|
||||
result = fetch(obj.conn, 'SELECT name FROM sqlite_master WHERE type="table"');
|
||||
obj.tableNames = result.name;
|
||||
end
|
||||
|
||||
|
||||
catch e
|
||||
error('Failed to retrieve table names: %s', e.message);
|
||||
end
|
||||
@@ -169,14 +169,6 @@ classdef DBHandler < handle
|
||||
function healthyDB = dbIsHealthy(obj)
|
||||
healthyDB = false;
|
||||
|
||||
num_runs = obj.fetch('SELECT COUNT(*) AS total_runs FROM Runs');
|
||||
|
||||
num_configs = obj.fetch('SELECT COUNT(*) AS total_configurations FROM Configurations');
|
||||
|
||||
num_meas = obj.fetch('SELECT COUNT(*) AS total_measurements FROM Measurements');
|
||||
|
||||
assert((num_runs{1,1}==num_configs{1,1})&&(num_configs{1,1}==num_meas{1,1}),'Different num of entries per table');
|
||||
|
||||
%Check for any duplicate paths
|
||||
duplictae_raw = obj.fetch("SELECT COALESCE(Runs.rx_raw_path,'NaN') AS rx_raw_path, COUNT(*) AS occurrences FROM Runs GROUP BY rx_raw_path HAVING COUNT(*) > 1");
|
||||
duplictae_sync = obj.fetch("SELECT COALESCE(Runs.rx_sync_path,'NaN') AS rx_sync_path, COUNT(*) AS occurrences FROM Runs GROUP BY rx_sync_path HAVING COUNT(*) > 1");
|
||||
@@ -190,7 +182,6 @@ classdef DBHandler < handle
|
||||
end
|
||||
|
||||
|
||||
|
||||
function lastID = appendToTable(obj, tableName, newRow)
|
||||
% appendToTable Appends a new row to the specified table
|
||||
%
|
||||
@@ -206,48 +197,52 @@ classdef DBHandler < handle
|
||||
error('Table %s does not exist in the database or has not been fetched.', tableName);
|
||||
end
|
||||
|
||||
% Convert newRow to a table if it is a struct
|
||||
% Handle struct preprocessing before table conversion
|
||||
if isstruct(newRow)
|
||||
fields = fieldnames(newRow);
|
||||
emptyFields = structfun(@isempty,newRow);
|
||||
|
||||
% Handle empty fields
|
||||
emptyFields = structfun(@isempty, newRow);
|
||||
if sum(emptyFields) > 0
|
||||
emptyFieldNames = fields(emptyFields); % use () to get a cell array
|
||||
emptyFieldNames = fields(emptyFields);
|
||||
for idx = 1:numel(emptyFieldNames)
|
||||
newRow.(emptyFieldNames{idx}) = NaN;
|
||||
end
|
||||
end
|
||||
|
||||
% Convert arrays to JSON strings
|
||||
for i = 1:length(fields)
|
||||
fieldValue = newRow.(fields{i});
|
||||
if isnumeric(fieldValue) && length(fieldValue) > 1
|
||||
newRow.(fields{i}) = jsonencode(fieldValue);
|
||||
end
|
||||
end
|
||||
|
||||
% Convert to table
|
||||
newRow = struct2table(newRow);
|
||||
end
|
||||
|
||||
% Ensure the new row matches the structure of the existing table
|
||||
existingTableStructure = obj.tables.(tableName);
|
||||
|
||||
% Perform data type checks and conversions
|
||||
% Perform remaining data type checks and conversions
|
||||
for colName = newRow.Properties.VariableNames
|
||||
% Extract the value and its intended column type
|
||||
value = newRow.(colName{1});
|
||||
existingValue = existingTableStructure.(colName{1});
|
||||
|
||||
% If the value is a class object, convert it to JSON format
|
||||
if isobject(value) && ~isdatetime(value) && ~isa(value,"string")
|
||||
if iscell(value) && ~isempty(value)
|
||||
if ischar(value{1}) || isstring(value{1})
|
||||
newRow.(colName{1}) = value{1};
|
||||
end
|
||||
elseif isobject(value) && ~isdatetime(value) && ~isa(value, "string")
|
||||
newRow.(colName{1}) = string(jsonencode(value));
|
||||
|
||||
% If the value is a character array, convert it to a string
|
||||
elseif ischar(value)
|
||||
newRow.(colName{1}) = string(value);
|
||||
|
||||
elseif isdatetime(value)
|
||||
|
||||
% newRow.(colName{1}) = string(value);
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
% Parameters for retry logic
|
||||
maxRetries = 50;
|
||||
basePause = 0.05; % seconds
|
||||
|
||||
attempt = 0;
|
||||
success = false;
|
||||
|
||||
@@ -257,36 +252,40 @@ classdef DBHandler < handle
|
||||
newRow_ = obj.convertTableToCellStrings(newRow);
|
||||
end
|
||||
sqlwrite(obj.conn, tableName, newRow);
|
||||
success = true; % Write successful
|
||||
success = true;
|
||||
catch e
|
||||
if contains(e.message, 'database is locked') || contains(e.message, 'cannot rollback transaction')
|
||||
attempt = attempt + 1;
|
||||
pauseTime = basePause * (1 + rand()); % Add random jitter to reduce collision chance
|
||||
fprintf('Database locked. Retry %d/%d after %.3f seconds...\n', attempt, maxRetries, pauseTime);
|
||||
pauseTime = basePause * (1 + rand());
|
||||
fprintf('Database locked. Retry %d/%d after %.3f seconds...\n', ...
|
||||
attempt, maxRetries, pauseTime);
|
||||
pause(pauseTime);
|
||||
else
|
||||
fprintf('Error details:\n');
|
||||
fprintf('Column values:\n');
|
||||
disp(newRow);
|
||||
error('Failed to append to the table %s: %s', tableName, e.message);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ~success
|
||||
error('Failed to append to table %s after %d retries due to database lock.', tableName, maxRetries);
|
||||
error('Failed to append to table %s after %d retries due to database lock.', ...
|
||||
tableName, maxRetries);
|
||||
end
|
||||
|
||||
% Retrieve the measurement_id of the newly inserted row for linking other tables
|
||||
|
||||
% Retrieve the ID of the newly inserted row
|
||||
if obj.type == "mysql"
|
||||
queue = "SELECT LAST_INSERT_ID()";
|
||||
query = "SELECT LAST_INSERT_ID()";
|
||||
elseif obj.type == "sqlite"
|
||||
queue = "SELECT last_insert_rowid()";
|
||||
query = "SELECT last_insert_rowid()";
|
||||
end
|
||||
|
||||
result = fetch(obj.conn, queue);
|
||||
lastID = result{1, 1}; % Access the value directly from the table
|
||||
result = fetch(obj.conn, query);
|
||||
lastID = result{1, 1};
|
||||
end
|
||||
|
||||
function exists = checkIfRunExists(obj, table2check, column2check, value2check)
|
||||
function [exists, count] = checkIfRunExists(obj, table2check, column2check, value2check)
|
||||
% checkIfRunExists Checks if a specific value exists in a specified column of a table
|
||||
%
|
||||
% Usage:
|
||||
@@ -311,7 +310,11 @@ classdef DBHandler < handle
|
||||
end
|
||||
|
||||
% Construct the query to check for the value in the specified column
|
||||
query = sprintf('SELECT COUNT(*) FROM %s WHERE %s = "%s"', table2check, column2check, value2check);
|
||||
if isnumeric(value2check)
|
||||
query = sprintf('SELECT COUNT(*) FROM %s WHERE %s = %d', table2check, column2check, value2check);
|
||||
else
|
||||
query = sprintf('SELECT COUNT(*) FROM %s WHERE %s = "%s"', table2check, column2check, value2check);
|
||||
end
|
||||
|
||||
% Execute the query and pass the value2check to avoid SQL injection issues
|
||||
try
|
||||
@@ -325,12 +328,21 @@ classdef DBHandler < handle
|
||||
exists = count > 0;
|
||||
|
||||
if exists
|
||||
disp(['The value "', value2check, '" already exists in the column "', column2check, '" of the table "', table2check, '".']);
|
||||
% disp(['The value "', num2str(value2check), '" already exists in the column "', column2check, '" of the table "', table2check, '".']);
|
||||
else
|
||||
% disp(['The value "', value2check, '" does not exist in the column "', column2check, '" of the table "', table2check, '".']);
|
||||
end
|
||||
end
|
||||
|
||||
function hashStr = calcHash(~,object)
|
||||
jsonStr = jsonencode(object);
|
||||
md = java.security.MessageDigest.getInstance('MD5');
|
||||
md.update(uint8(jsonStr));
|
||||
hashBytes = typecast(md.digest, 'uint8');
|
||||
hashStr = lower(dec2hex(hashBytes)');
|
||||
hashStr = lower(strtrim(hashStr(:)')); % Convert to a lowercase string
|
||||
end
|
||||
|
||||
|
||||
function resultID = addProcessingResult(obj, run_id, resultData, eqParamsData)
|
||||
% addProcessingResult Adds a processing result and links it to an EqualizerParameters entry.
|
||||
@@ -348,18 +360,19 @@ classdef DBHandler < handle
|
||||
% resultID: The result_id of the newly inserted ProcessingResults entry.
|
||||
|
||||
% 1. Compute hash for equalizer parameters
|
||||
jsonStr = jsonencode(eqParamsData);
|
||||
md = java.security.MessageDigest.getInstance('MD5');
|
||||
md.update(uint8(jsonStr));
|
||||
hashBytes = typecast(md.digest, 'uint8');
|
||||
hashStr = lower(dec2hex(hashBytes)');
|
||||
hashStr = lower(strtrim(hashStr(:)')); % Convert to a lowercase string
|
||||
% jsonStr = jsonencode(eqParamsData);
|
||||
% md = java.security.MessageDigest.getInstance('MD5');
|
||||
% md.update(uint8(jsonStr));
|
||||
% hashBytes = typecast(md.digest, 'uint8');
|
||||
% hashStr = lower(dec2hex(hashBytes)');
|
||||
% hashStr = lower(strtrim(hashStr(:)')); % Convert to a lowercase string
|
||||
hashStr = obj.calcHash(eqParamsData);
|
||||
|
||||
% Add hash to equalizer parameters
|
||||
eqParamsData.config_hash = hashStr;
|
||||
eqParamsData.hash = hashStr;
|
||||
|
||||
% 2. Check if an equalizer configuration with the same hash exists
|
||||
queryStr = sprintf('SELECT eq_id FROM EqualizerParameters WHERE config_hash = ''%s''', eqParamsData.config_hash);
|
||||
queryStr = sprintf('SELECT eq_id FROM Equalizer WHERE hash = ''%s''', eqParamsData.hash);
|
||||
existingEntry = obj.fetch(queryStr);
|
||||
|
||||
if ~isempty(existingEntry)
|
||||
@@ -367,27 +380,30 @@ classdef DBHandler < handle
|
||||
eq_id = existingEntry{1,1};
|
||||
else
|
||||
% Insert the new equalizer configuration and get its eq_id
|
||||
eq_id = obj.appendToTable('EqualizerParameters', eqParamsData);
|
||||
eqParamsData = eqParamsData.toStruct;
|
||||
eq_id = obj.appendToTable('Equalizer', eqParamsData);
|
||||
end
|
||||
|
||||
% 3. Add the equalizer configuration reference and run_id to resultData
|
||||
resultData.eqParam_id = eq_id;
|
||||
resultData = resultData.toStruct;
|
||||
resultData.eq_id = eq_id;
|
||||
resultData.run_id = run_id;
|
||||
|
||||
% 4. Compute hash for the processing result
|
||||
tempResultData = rmfield(resultData, 'date_of_processing');
|
||||
resultJsonStr = jsonencode(tempResultData);
|
||||
md2 = java.security.MessageDigest.getInstance('MD5'); % Create a new MD5 instance
|
||||
md2.update(uint8(resultJsonStr));
|
||||
resultHashBytes = typecast(md2.digest, 'uint8');
|
||||
resultHashStr = lower(dec2hex(resultHashBytes)');
|
||||
resultHashStr = lower(strtrim(resultHashStr(:)')); % Convert to a lowercase string
|
||||
% resultJsonStr = jsonencode(tempResultData);
|
||||
% md2 = java.security.MessageDigest.getInstance('MD5'); % Create a new MD5 instance
|
||||
% md2.update(uint8(resultJsonStr));
|
||||
% resultHashBytes = typecast(md2.digest, 'uint8');
|
||||
% resultHashStr = lower(dec2hex(resultHashBytes)');
|
||||
% resultHashStr = lower(strtrim(resultHashStr(:)')); % Convert to a lowercase string
|
||||
resultHashStr = obj.calcHash(tempResultData);
|
||||
|
||||
% Add the result hash to resultData
|
||||
resultData.result_hash = resultHashStr;
|
||||
resultData.hash = resultHashStr;
|
||||
|
||||
% 5. Check if an identical processing result already exists
|
||||
queryStr2 = sprintf('SELECT result_id FROM Results WHERE result_hash = ''%s''', resultData.result_hash);
|
||||
queryStr2 = sprintf('SELECT result_id FROM Results WHERE hash = ''%s''', resultData.hash);
|
||||
existingResult = obj.fetch(queryStr2);
|
||||
|
||||
if ~isempty(existingResult)
|
||||
@@ -397,7 +413,66 @@ classdef DBHandler < handle
|
||||
return;
|
||||
end
|
||||
|
||||
% 6. Insert the processing result
|
||||
% 6. check if obj.tables.Results matches Metricstruct
|
||||
% Fields to exclude from comparison
|
||||
excludeFields = {'result_id', 'run_id', 'eq_id'};
|
||||
|
||||
% 7. Chack for new fields in Metric struct and append to
|
||||
% database if necessary
|
||||
ms=Metricstruct;
|
||||
metricFields = setdiff(fieldnames(ms), excludeFields);
|
||||
tableFields = setdiff(fieldnames(obj.tables.Results), excludeFields);
|
||||
|
||||
% Check matches and find missing fields
|
||||
matches = all(ismember(metricFields, tableFields));
|
||||
|
||||
if ~matches
|
||||
missingFields = setdiff(metricFields, tableFields);
|
||||
|
||||
|
||||
% If there are missing fields, add them to the SQL table
|
||||
if ~isempty(missingFields)
|
||||
for i = 1:length(missingFields)
|
||||
fieldName = missingFields{i};
|
||||
|
||||
% Determine SQL data type based on MATLAB class
|
||||
fieldValue = ms.(fieldName);
|
||||
if isnumeric(fieldValue)
|
||||
if isinteger(fieldValue)
|
||||
sqlType = 'INTEGER';
|
||||
else
|
||||
sqlType = 'REAL';
|
||||
end
|
||||
elseif ischar(fieldValue) || isstring(fieldValue)
|
||||
sqlType = 'TEXT';
|
||||
elseif isdatetime(fieldValue)
|
||||
sqlType = 'DATETIME';
|
||||
elseif iscell(fieldValue) || isstruct(fieldValue) || islogical(fieldValue)
|
||||
sqlType = 'TEXT'; % Store as JSON
|
||||
else
|
||||
sqlType = 'TEXT'; % Default to TEXT for unknown types
|
||||
end
|
||||
|
||||
% Create ALTER TABLE query
|
||||
queryStr = sprintf('ALTER TABLE Results ADD COLUMN %s %s', fieldName, sqlType);
|
||||
|
||||
try
|
||||
% Execute the query
|
||||
obj.fetch(queryStr);
|
||||
fprintf('Added field "%s" of type %s to Results table\n', fieldName, sqlType);
|
||||
catch ME
|
||||
fprintf('Error adding field "%s": %s\n', fieldName, ME.message);
|
||||
end
|
||||
end
|
||||
else
|
||||
fprintf('No missing fields to add.\n');
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
% 8. Insert the processing result
|
||||
resultID = obj.appendToTable('Results', resultData);
|
||||
end
|
||||
|
||||
@@ -486,8 +561,6 @@ classdef DBHandler < handle
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
function executeSQL(obj, query)
|
||||
% This method executes an SQL statement using MATLAB's execute function.
|
||||
execute(obj.conn, query);
|
||||
@@ -518,22 +591,6 @@ classdef DBHandler < handle
|
||||
selectedFields = [];
|
||||
end
|
||||
|
||||
% Step 1: Prompt the user to input filter parameters if not provided
|
||||
if isempty(filterParams)
|
||||
filterParams = obj.promptFilterParameters();
|
||||
end
|
||||
|
||||
% Step 2: Prompt the user to select fields to include in the SELECT statement
|
||||
if isempty(selectedFields)
|
||||
selectedFields = obj.promptSelectFields();
|
||||
else
|
||||
if iscell(selectedFields)
|
||||
|
||||
elseif isstruct(selectedFields)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
% Step 3: Construct the SQL query based on the inputs
|
||||
query = obj.constructSQLQuery(filterParams, selectedFields);
|
||||
|
||||
@@ -619,16 +676,20 @@ classdef DBHandler < handle
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function query = constructSQLQuery(obj, filterParams, selectedFields)
|
||||
% constructSQLQuery Constructs the SQL query based on filter parameters and selected fields.
|
||||
%
|
||||
% If selectedFields is provided as a struct, it is converted to a cell array.
|
||||
% The conversion takes the field names and creates entries like:
|
||||
% {'selectedFields.fieldName'} for each field.
|
||||
|
||||
% Input check for selectedFields: if it's a struct, convert it to a cell array.
|
||||
if isstruct(selectedFields)
|
||||
arguments
|
||||
obj
|
||||
filterParams
|
||||
selectedFields
|
||||
end
|
||||
|
||||
% Step 1: Handle selectedFields conversion
|
||||
if isempty(selectedFields) || (ischar(selectedFields) && strcmpi(selectedFields, 'all'))
|
||||
selectedFields = obj.getTableFieldNames('Runs'); % Default to Runs table
|
||||
elseif isstruct(selectedFields)
|
||||
% Convert struct to cell array of 'Table.field' format
|
||||
newFields = {};
|
||||
tableNames = fieldnames(selectedFields);
|
||||
for t = 1:numel(tableNames)
|
||||
@@ -643,39 +704,10 @@ classdef DBHandler < handle
|
||||
selectedFields = newFields;
|
||||
end
|
||||
|
||||
% Construct the SELECT clause dynamically based on user selection.
|
||||
% (Assuming that when provided as a cell array, each entry is of the form
|
||||
% 'TableName.fieldName' or, in our conversion case, 'selectedFields.fieldName'.)
|
||||
selectClause = 'SELECT DISTINCT ';
|
||||
for i = 1:numel(selectedFields)
|
||||
fieldParts = strsplit(selectedFields{i}, '.');
|
||||
% If the field comes from the struct conversion, its first part is 'selectedFields'
|
||||
% and the actual field name is in the second part.
|
||||
if strcmp(fieldParts{1}, 'selectedFields')
|
||||
tableName = fieldParts{1}; % not used for type checking below
|
||||
fieldName = fieldParts{2};
|
||||
else
|
||||
tableName = fieldParts{1};
|
||||
fieldName = fieldParts{2};
|
||||
end
|
||||
% Step 2: Generate COALESCE string for SELECT clause
|
||||
selectClause = obj.generateCoalesceString(selectedFields);
|
||||
|
||||
% Decide on COALESCE depending on the field type.
|
||||
% If the table is known in obj.tables and the field is numeric, use 'NaN'.
|
||||
if isfield(obj.tables, tableName) && isfield(obj.tables.(tableName), fieldName) && isnumeric(obj.tables.(tableName).(fieldName))
|
||||
selectClause = [selectClause, 'COALESCE(', selectedFields{i}, ', ''NaN'') AS ', fieldName];
|
||||
else
|
||||
selectClause = [selectClause, 'COALESCE(', selectedFields{i}, ', '''') AS ', fieldName];
|
||||
end
|
||||
|
||||
if i < numel(selectedFields)
|
||||
selectClause = [selectClause, ', '];
|
||||
else
|
||||
selectClause = [selectClause, ' '];
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
% --- Adaptive FROM Clause ---
|
||||
% Step 3: Generate FROM clause with joins
|
||||
mainTable = 'Runs';
|
||||
fromClause = ['FROM ', mainTable, ' '];
|
||||
|
||||
@@ -693,58 +725,90 @@ classdef DBHandler < handle
|
||||
|
||||
if isfield(obj.tables.(tableName), 'run_id')
|
||||
% Direct join to Runs
|
||||
normalJoins = [normalJoins, 'LEFT JOIN ', tableName, ' ON ', mainTable, '.run_id = ', tableName, '.run_id '];
|
||||
normalJoins = [normalJoins, 'LEFT JOIN ', tableName, ...
|
||||
' ON ', mainTable, '.run_id = ', tableName, '.run_id '];
|
||||
elseif isfield(obj.tables.(tableName), 'eq_id')
|
||||
% Equalizer depends on Results, collect this join separately
|
||||
equalizerJoin = ['LEFT JOIN ', tableName, ' ON Results.eqParam_id = ', tableName, '.eq_id '];
|
||||
equalizerJoin = ['LEFT JOIN ', tableName, ...
|
||||
' ON Results.eq_id = ', tableName, '.eq_id '];
|
||||
end
|
||||
end
|
||||
|
||||
% Combine joins: normal joins first, Equalizer last
|
||||
fromClause = [fromClause, normalJoins, equalizerJoin];
|
||||
|
||||
% Step 4: Generate WHERE clause if filter parameters exist
|
||||
if ~isempty(filterParams)
|
||||
whereClause = obj.generateWhereClause(filterParams);
|
||||
if ~isempty(whereClause)
|
||||
query = [selectClause, ' ', fromClause, 'WHERE ', whereClause];
|
||||
else
|
||||
query = [selectClause, ' ', fromClause];
|
||||
end
|
||||
else
|
||||
query = [selectClause, ' ', fromClause];
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
% --- WHERE Clause Construction ---
|
||||
baseQuery = [selectClause, ' ', fromClause, 'WHERE '];
|
||||
function whereClause = generateWhereClause(obj, filterParams)
|
||||
filterClauses = [];
|
||||
tableNames_ = fieldnames(filterParams);
|
||||
for t = 1:numel(tableNames_)
|
||||
tableName = tableNames_{t};
|
||||
|
||||
% If filterParams is a DbFilterParameter object, get its internal structure
|
||||
if isa(filterParams, 'QueryFilter')
|
||||
filterParams = filterParams.toStruct();
|
||||
end
|
||||
|
||||
% Now proceed with the structure
|
||||
tableNames = fieldnames(filterParams);
|
||||
|
||||
for t = 1:numel(tableNames)
|
||||
tableName = tableNames{t};
|
||||
tableParams = filterParams.(tableName);
|
||||
fieldNames = fieldnames(tableParams);
|
||||
|
||||
for i = 1:numel(fieldNames)
|
||||
fieldName = fieldNames{i};
|
||||
value = tableParams.(fieldName);
|
||||
fullName = sprintf('%s.%s', tableName, fieldName);
|
||||
|
||||
% Handle various types of values for SQL query construction
|
||||
if isempty(value)
|
||||
% Skip empty values
|
||||
if isempty(value) || (isa(value, 'QueryFilter') && isempty(value.value))
|
||||
continue;
|
||||
elseif isnumeric(value) && isnan(value)
|
||||
filterClause = sprintf('%s IS NULL', fullName);
|
||||
elseif isnumeric(value) && ~isEnumeration(value)
|
||||
filterClause = sprintf('%s = %f', fullName, value);
|
||||
elseif islogical(value) || (isnumeric(value) && ismember(value, [0, 1])) && ~isEnumeration(value)
|
||||
filterClause = sprintf('%s = %d', fullName, value);
|
||||
elseif ischar(value) || isstring(value)
|
||||
filterClause = sprintf('%s = ''%s''', fullName, char(value));
|
||||
elseif isEnumeration(value)
|
||||
filterClause = sprintf('%s = ''%s''', fullName, value);
|
||||
else
|
||||
error('Unsupported data type for field "%s".', fullName);
|
||||
end
|
||||
filterClauses = [filterClauses, filterClause, ' AND '];
|
||||
|
||||
% Handle Filter class
|
||||
if isa(value, 'SqlFilter')
|
||||
if isnumeric(value.value)
|
||||
filterClause = sprintf('%s %s %f', ...
|
||||
fullName, value.operator, value.value);
|
||||
elseif ischar(value.value) || isstring(value.value)
|
||||
filterClause = sprintf('%s %s ''%s''', ...
|
||||
fullName, value.operator, char(value.value));
|
||||
else
|
||||
continue; % Skip unsupported types
|
||||
end
|
||||
filterClauses = [filterClauses, filterClause, ' AND '];
|
||||
else
|
||||
% Handle direct values (legacy support)
|
||||
if isnumeric(value) && isnan(value)
|
||||
filterClause = sprintf('%s IS NULL', fullName);
|
||||
elseif isnumeric(value)
|
||||
filterClause = sprintf('%s = %f', fullName, value);
|
||||
elseif ischar(value) || isstring(value)
|
||||
filterClause = sprintf('%s = ''%s''', fullName, char(value));
|
||||
else
|
||||
continue; % Skip unsupported types
|
||||
end
|
||||
filterClauses = [filterClauses, filterClause, ' AND '];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% Remove trailing ' AND ' if any filters were added.
|
||||
% Remove trailing ' AND ' if any filters were added
|
||||
if ~isempty(filterClauses)
|
||||
filterClauses = filterClauses(1:end-5);
|
||||
query = [selectClause, ' ', fromClause, 'WHERE ', filterClauses];
|
||||
whereClause = filterClauses(1:end-5);
|
||||
else
|
||||
query = [selectClause, ' ', fromClause];
|
||||
whereClause = '';
|
||||
end
|
||||
end
|
||||
|
||||
@@ -980,5 +1044,59 @@ classdef DBHandler < handle
|
||||
end
|
||||
|
||||
|
||||
function fieldNames = getTableFieldNames(obj, tableName)
|
||||
% Returns all field names for a given table as a cell array in the format {'TableName.fieldName'}
|
||||
if isfield(obj.tables, tableName)
|
||||
% Get raw field names
|
||||
rawFields = fieldnames(obj.tables.(tableName));
|
||||
|
||||
% Create cell array with table name prefix
|
||||
fieldNames = cellfun(@(x) [tableName, '.', x], ...
|
||||
rawFields, ...
|
||||
'UniformOutput', false);
|
||||
else
|
||||
error('Table "%s" not found in obj.tables.', tableName);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function coalesceStr = generateCoalesceString(obj, selectedFields)
|
||||
% Generates a COALESCE string for selected fields
|
||||
% Input:
|
||||
% selectedFields: cell array of strings in format {'Table.field'}
|
||||
% e.g., {'Runs.run_id', 'Runs.bitrate'}
|
||||
% Output:
|
||||
% coalesceStr: string with COALESCE statements
|
||||
|
||||
arguments
|
||||
obj
|
||||
selectedFields cell
|
||||
end
|
||||
|
||||
% Initialize cell array to store each COALESCE statement
|
||||
coalesceStatements = cell(length(selectedFields), 1);
|
||||
|
||||
% Generate COALESCE statement for each field
|
||||
for i = 1:length(selectedFields)
|
||||
% Split table and field name
|
||||
parts = strsplit(selectedFields{i}, '.');
|
||||
if length(parts) ~= 2
|
||||
error('Field name must be in format "Table.field": %s', selectedFields{i});
|
||||
end
|
||||
tableName = parts{1};
|
||||
fieldName = parts{2};
|
||||
|
||||
% Generate COALESCE statement
|
||||
coalesceStatements{i} = sprintf('COALESCE(%s.%s, ''NaN'') AS %s ', ...
|
||||
tableName, fieldName, fieldName);
|
||||
end
|
||||
|
||||
% Join with comma, newline and MATLAB string continuation
|
||||
coalesceStr = strjoin(coalesceStatements, [', ' sprintf('\n ')]);
|
||||
% Add initial newline and spacing for formatting
|
||||
coalesceStr = [sprintf('SELECT DISTINCT \n ') coalesceStr];
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
68
Classes/DataBaseHandler/Equalizerstruct.m
Normal file
68
Classes/DataBaseHandler/Equalizerstruct.m
Normal file
@@ -0,0 +1,68 @@
|
||||
classdef Equalizerstruct
|
||||
% Equalizerstruct - Class to store and manage equalizer structure data
|
||||
|
||||
properties
|
||||
eq_id (1,1) double {mustBeNumeric} = NaN
|
||||
equalizer_structure equalizer_structure = equalizer_structure.ffe
|
||||
eq
|
||||
mlse
|
||||
comment char = string.empty() % Changed to string with proper empty initialization
|
||||
hash char = string.empty() % Changed to string with proper empty initialization
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = Equalizerstruct(varargin)
|
||||
% Constructor method for Equalizerstruct
|
||||
% Can be called empty or with name-value pairs
|
||||
|
||||
if nargin > 0
|
||||
for i = 1:2:nargin
|
||||
if isprop(obj, varargin{i})
|
||||
obj.(varargin{i}) = varargin{i+1};
|
||||
else
|
||||
error('Property %s does not exist in Equalizerstruct', varargin{i});
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function s = toStruct(obj)
|
||||
% Convert the object to a struct
|
||||
s = struct();
|
||||
props = properties(obj);
|
||||
for i = 1:length(props)
|
||||
s.(props{i}) = obj.(props{i});
|
||||
end
|
||||
end
|
||||
|
||||
function str = toString(obj)
|
||||
% Convert the object to a formatted string
|
||||
s = obj.toStruct();
|
||||
str = sprintf('Equalizerstruct:\n');
|
||||
fields = fieldnames(s);
|
||||
for i = 1:length(fields)
|
||||
val = s.(fields{i});
|
||||
if isempty(val)
|
||||
str = sprintf('%s%s: []\n', str, fields{i});
|
||||
elseif isnumeric(val) && length(val) > 1
|
||||
str = sprintf('%s%s: [%s]\n', str, fields{i}, num2str(val'));
|
||||
else
|
||||
str = sprintf('%s%s: %s\n', str, fields{i}, string(val));
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
methods (Static)
|
||||
function obj = fromStruct(s)
|
||||
% Create an Equalizerstruct object from a struct
|
||||
obj = Equalizerstruct();
|
||||
fields = fieldnames(s);
|
||||
for i = 1:length(fields)
|
||||
if isprop(obj, fields{i})
|
||||
obj.(fields{i}) = s.(fields{i});
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
87
Classes/DataBaseHandler/Metricstruct.m
Normal file
87
Classes/DataBaseHandler/Metricstruct.m
Normal file
@@ -0,0 +1,87 @@
|
||||
classdef Metricstruct
|
||||
% ResultData - Class to store and manage metric data from signal processing results
|
||||
|
||||
properties
|
||||
result_id (1,1) double {mustBeNumeric} = NaN
|
||||
run_id (1,1) double {mustBeNumeric} = NaN
|
||||
eqParam_id (1,1) double {mustBeNumeric} = NaN
|
||||
date_of_processing (1,1) datetime = datetime('now')
|
||||
|
||||
numBits (1,1) double {mustBeInteger, mustBeNonnegative} = 0
|
||||
BER (1,1) double {mustBeNumeric, mustBeNonnegative, mustBeLessThanOrEqual(BER,1)} = 0
|
||||
numBitErr (1,1) double {mustBeInteger, mustBeNonnegative} = 0
|
||||
BER_precoded (1,1) double {mustBeNumeric, mustBeNonnegative, mustBeLessThanOrEqual(BER_precoded,1)} = 0
|
||||
numBitErr_precoded (1,1) double {mustBeInteger, mustBeNonnegative} = 0
|
||||
|
||||
SNR (1,1) double {mustBeNumeric} = NaN
|
||||
SNR_level (:,1) double {mustBeNumeric} = []
|
||||
STD (1,1) double {mustBeNumeric} = NaN
|
||||
STD_level (:,1) double {mustBeNumeric, mustBeNonnegative} = []
|
||||
STDrx (1,1) double {mustBeNumeric} = NaN
|
||||
STDrx_level (:,1) double {mustBeNumeric, mustBeNonnegative} = []
|
||||
EVM (1,1) double {mustBeNumeric} = NaN
|
||||
EVM_level (:,1) double {mustBeNumeric} = []
|
||||
|
||||
GMI (1,1) double {mustBeNumeric} = NaN
|
||||
AIR (1,1) double {mustBeNumeric} = NaN
|
||||
Alpha (:,1) double {mustBeNumeric} = []
|
||||
MLSE_dir (:,1) double {mustBeNumeric} = []
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = Metricstruct(varargin)
|
||||
% Constructor method for ResultData
|
||||
% Can be called empty or with name-value pairs
|
||||
|
||||
% Process name-value pairs if provided
|
||||
if nargin > 0
|
||||
for i = 1:2:nargin
|
||||
if isprop(obj, varargin{i})
|
||||
obj.(varargin{i}) = varargin{i+1};
|
||||
else
|
||||
error('Property %s does not exist in ResultData', varargin{i});
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function s = toStruct(obj)
|
||||
% Convert the object to a struct
|
||||
s = struct();
|
||||
props = properties(obj);
|
||||
for i = 1:length(props)
|
||||
s.(props{i}) = obj.(props{i});
|
||||
end
|
||||
end
|
||||
|
||||
function str = toString(obj)
|
||||
% Convert the object to a formatted string
|
||||
s = obj.toStruct();
|
||||
str = sprintf('ResultData:\n');
|
||||
fields = fieldnames(s);
|
||||
for i = 1:length(fields)
|
||||
val = s.(fields{i});
|
||||
if isempty(val)
|
||||
str = sprintf('%s%s: []\n', str, fields{i});
|
||||
elseif isnumeric(val) && length(val) > 1
|
||||
str = sprintf('%s%s: [%s]\n', str, fields{i}, num2str(val'));
|
||||
else
|
||||
str = sprintf('%s%s: %s\n', str, fields{i}, string(val));
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
methods (Static)
|
||||
function obj = fromStruct(s)
|
||||
% Create a ResultData object from a struct
|
||||
obj = Metricstruct();
|
||||
fields = fieldnames(s);
|
||||
for i = 1:length(fields)
|
||||
if isprop(obj, fields{i})
|
||||
obj.(fields{i}) = s.(fields{i});
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
169
Classes/DataBaseHandler/QueryFilter.m
Normal file
169
Classes/DataBaseHandler/QueryFilter.m
Normal file
@@ -0,0 +1,169 @@
|
||||
% QueryFilter - Class for building SQL WHERE conditions for database queries.
|
||||
%
|
||||
% Usage:
|
||||
% qf = QueryFilter();
|
||||
% qf.where('Runs', 'fiber_length', SqlOperator.GREATER_THAN, 5);
|
||||
% qf.where('Runs', 'bitrate', SqlOperator.IN, [224, 336, 448]);
|
||||
% qf.where('Runs', 'is_mpi', SqlOperator.EQUALS, 0);
|
||||
%
|
||||
% % Convert to struct for use in DBHandler or other query functions:
|
||||
% filterStruct = qf.toStruct();
|
||||
%
|
||||
% % Display current filters:
|
||||
% disp(qf);
|
||||
|
||||
classdef QueryFilter < handle
|
||||
|
||||
|
||||
properties (Access = private)
|
||||
filters struct = struct()
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = QueryFilter(oldFormat)
|
||||
% Constructor - optionally convert from old filter format
|
||||
if nargin > 0 && isstruct(oldFormat)
|
||||
tableNames = fieldnames(oldFormat);
|
||||
for t = 1:length(tableNames)
|
||||
tableName = tableNames{t};
|
||||
if isstruct(oldFormat.(tableName))
|
||||
fields = fieldnames(oldFormat.(tableName));
|
||||
for f = 1:length(fields)
|
||||
fieldName = fields{f};
|
||||
value = oldFormat.(tableName).(fieldName);
|
||||
if ~isempty(value)
|
||||
obj.where(tableName, fieldName, value);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function where(obj, tableName, fieldName, operator, value)
|
||||
% Add a WHERE condition to the query
|
||||
%
|
||||
% Inputs:
|
||||
% tableName - Name of the database table (char)
|
||||
% fieldName - Name of the field/column (char)
|
||||
% operator - SqlOperator enum or value if using EQUALS
|
||||
% value - Value to filter by
|
||||
%
|
||||
% Example:
|
||||
% filter.where('Runs', 'fiber_length', SqlOperator.GREATER_THAN, 5)
|
||||
arguments
|
||||
obj
|
||||
tableName char
|
||||
fieldName char
|
||||
operator SqlOperator
|
||||
value = []
|
||||
end
|
||||
|
||||
% Handle case where operator is the value (using default EQUALS)
|
||||
if nargin < 5
|
||||
value = operator;
|
||||
operator = SqlOperator.EQUALS;
|
||||
end
|
||||
|
||||
% Validate operator type
|
||||
if ~isa(operator, 'SqlOperator')
|
||||
error('Operator must be a SqlOperator enumeration');
|
||||
end
|
||||
|
||||
% Validate value based on operator
|
||||
obj.validateValue(operator, value);
|
||||
|
||||
% Create filter
|
||||
filter = SqlFilter(value, operator.toSqlString());
|
||||
|
||||
% Add to filters
|
||||
if ~isfield(obj.filters, tableName)
|
||||
obj.filters.(tableName) = struct();
|
||||
end
|
||||
obj.filters.(tableName).(fieldName) = filter;
|
||||
end
|
||||
|
||||
function s = toStruct(obj)
|
||||
% Convert filters to struct format for database query
|
||||
s = obj.filters;
|
||||
end
|
||||
|
||||
function display(obj)
|
||||
% Custom display of filter conditions
|
||||
fprintf('QueryFilter with conditions:\n');
|
||||
if isempty(fieldnames(obj.filters))
|
||||
fprintf(' No filters set\n');
|
||||
return;
|
||||
end
|
||||
|
||||
tables = fieldnames(obj.filters);
|
||||
for t = 1:length(tables)
|
||||
tableName = tables{t};
|
||||
if ~isempty(fieldnames(obj.filters.(tableName)))
|
||||
fprintf('\nTable: %s\n', tableName);
|
||||
fields = fieldnames(obj.filters.(tableName));
|
||||
for f = 1:length(fields)
|
||||
fieldName = fields{f};
|
||||
filter = obj.filters.(tableName).(fieldName);
|
||||
if isnumeric(filter.value)
|
||||
if length(filter.value) > 1
|
||||
valueStr = ['[', num2str(filter.value), ']'];
|
||||
else
|
||||
valueStr = num2str(filter.value);
|
||||
end
|
||||
elseif isempty(filter.value)
|
||||
valueStr = 'empty';
|
||||
else
|
||||
valueStr = char(filter.value);
|
||||
end
|
||||
fprintf(' %s %s %s\n', fieldName, filter.operator, valueStr);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function clear(obj, tableName)
|
||||
% Clear all filters or filters for a specific table
|
||||
if nargin < 2
|
||||
obj.filters = struct();
|
||||
else
|
||||
if isfield(obj.filters, tableName)
|
||||
obj.filters = rmfield(obj.filters, tableName);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function remove(obj, tableName, fieldName)
|
||||
% Remove a specific filter
|
||||
if isfield(obj.filters, tableName) && ...
|
||||
isfield(obj.filters.(tableName), fieldName)
|
||||
obj.filters.(tableName) = rmfield(obj.filters.(tableName), fieldName);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
methods (Access = private)
|
||||
function validateValue(~, operator, value)
|
||||
% Validate value based on operator type
|
||||
switch operator
|
||||
case SqlOperator.BETWEEN
|
||||
if ~isnumeric(value) || length(value) ~= 2
|
||||
error('BETWEEN operator requires array of 2 numbers');
|
||||
end
|
||||
case SqlOperator.IN
|
||||
if ~isnumeric(value) || isempty(value)
|
||||
error('IN operator requires non-empty array');
|
||||
end
|
||||
case SqlOperator.LIKE
|
||||
if ~ischar(value) && ~isstring(value)
|
||||
error('LIKE operator requires string value');
|
||||
end
|
||||
otherwise
|
||||
% For other operators, just ensure value is not empty
|
||||
if isempty(value)
|
||||
error('Value cannot be empty');
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
Classes/DataBaseHandler/SqlFilter.m
Normal file
22
Classes/DataBaseHandler/SqlFilter.m
Normal file
@@ -0,0 +1,22 @@
|
||||
% SqlFilter - Internal class for holding a value and SQL operator for a filter condition.
|
||||
%
|
||||
% Usage:
|
||||
% f = SqlFilter(10, SqlOperator.GREATER_THAN.toSqlString());
|
||||
% % f.value == 10, f.operator == '>'
|
||||
|
||||
|
||||
classdef SqlFilter
|
||||
properties
|
||||
value
|
||||
operator char = '='
|
||||
end
|
||||
|
||||
methods
|
||||
function obj = SqlFilter(value, operator)
|
||||
obj.value = value;
|
||||
if nargin > 1
|
||||
obj.operator = operator;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
48
Classes/DataBaseHandler/SqlOperator.m
Normal file
48
Classes/DataBaseHandler/SqlOperator.m
Normal file
@@ -0,0 +1,48 @@
|
||||
% SqlOperator - Enumeration of supported SQL operators for query building.
|
||||
%
|
||||
% Usage:
|
||||
% op = SqlOperator.GREATER_THAN;
|
||||
% opStr = op.toSqlString(); % returns '>'
|
||||
%
|
||||
% Supported operators:
|
||||
% EQUALS, GREATER_THAN, LESS_THAN, GREATER_EQUAL, LESS_EQUAL,
|
||||
% NOT_EQUAL, IN, BETWEEN, LIKE
|
||||
|
||||
classdef SqlOperator < uint32
|
||||
enumeration
|
||||
EQUALS (1) % ==
|
||||
GREATER_THAN (2) % >
|
||||
LESS_THAN (3) % <
|
||||
GREATER_EQUAL (4) % >=
|
||||
LESS_EQUAL (5) % <=
|
||||
NOT_EQUAL (6) % !=
|
||||
IN (7) % IN
|
||||
BETWEEN (8) % BETWEEN
|
||||
LIKE (9) % LIKE
|
||||
end
|
||||
|
||||
methods
|
||||
function op = toSqlString(obj)
|
||||
switch obj
|
||||
case SqlOperator.EQUALS
|
||||
op = '=';
|
||||
case SqlOperator.GREATER_THAN
|
||||
op = '>';
|
||||
case SqlOperator.LESS_THAN
|
||||
op = '<';
|
||||
case SqlOperator.GREATER_EQUAL
|
||||
op = '>=';
|
||||
case SqlOperator.LESS_EQUAL
|
||||
op = '<=';
|
||||
case SqlOperator.NOT_EQUAL
|
||||
op = '!=';
|
||||
case SqlOperator.IN
|
||||
op = 'IN';
|
||||
case SqlOperator.BETWEEN
|
||||
op = 'BETWEEN';
|
||||
case SqlOperator.LIKE
|
||||
op = 'LIKE';
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,76 +0,0 @@
|
||||
function copyStylingFrom(figNumSource, figNumTgt)
|
||||
% Get handles to the source and target figures
|
||||
sourceFig = figure(figNumSource);
|
||||
targetFig = figure(figNumTgt);
|
||||
|
||||
% Get axes of source and target figures
|
||||
sourceAxes = findall(sourceFig, 'type', 'axes');
|
||||
targetAxes = findall(targetFig, 'type', 'axes');
|
||||
|
||||
% Ensure the number of axes match
|
||||
if length(sourceAxes) ~= length(targetAxes)
|
||||
error('Number of axes in source and target figures must be the same.');
|
||||
end
|
||||
|
||||
% Loop through each pair of axes and copy styling properties
|
||||
for i = 1:length(sourceAxes)
|
||||
copyAxesProperties(sourceAxes(i), targetAxes(i));
|
||||
end
|
||||
|
||||
% Apply general figure properties if desired
|
||||
targetFig.Color = sourceFig.Color; % Background color
|
||||
end
|
||||
|
||||
function copyAxesProperties(sourceAx, targetAx)
|
||||
% List of properties to copy from source to target axes
|
||||
propsToCopy = {'XColor', 'YColor', 'ZColor', 'FontSize', 'FontName', ...
|
||||
'GridColor', 'GridLineStyle', 'MinorGridColor', 'Box', ...
|
||||
'XGrid', 'YGrid', 'ZGrid', 'XMinorGrid', 'YMinorGrid', 'ZMinorGrid', ...
|
||||
'LineWidth', 'TitleFontSizeMultiplier', 'LabelFontSizeMultiplier'};
|
||||
|
||||
% Copy properties from source to target
|
||||
for i = 1:length(propsToCopy)
|
||||
try
|
||||
targetAx.(propsToCopy{i}) = sourceAx.(propsToCopy{i});
|
||||
catch
|
||||
% Skip property if it doesn't exist or can't be copied
|
||||
end
|
||||
end
|
||||
|
||||
% Copy axis labels and titles
|
||||
targetAx.Title.String = sourceAx.Title.String;
|
||||
targetAx.XLabel.String = sourceAx.XLabel.String;
|
||||
targetAx.YLabel.String = sourceAx.YLabel.String;
|
||||
targetAx.ZLabel.String = sourceAx.ZLabel.String;
|
||||
|
||||
% Copy children elements like lines, patches, etc.
|
||||
sourceChildren = allchild(sourceAx);
|
||||
targetChildren = allchild(targetAx);
|
||||
|
||||
% Ensure the number of children elements match
|
||||
if length(sourceChildren) ~= length(targetChildren)
|
||||
warning('Number of elements in source and target axes differ. Styling may not be applied completely.');
|
||||
end
|
||||
|
||||
% Copy properties of children (like lines, patches, etc.), except colors and legends
|
||||
for i = 1:min(length(sourceChildren), length(targetChildren))
|
||||
copyObjectProperties(sourceChildren(i), targetChildren(i));
|
||||
end
|
||||
end
|
||||
|
||||
function copyObjectProperties(sourceObj, targetObj)
|
||||
% List of common properties to copy for plot elements (lines, patches, etc.)
|
||||
propsToCopy = {'LineStyle', 'LineWidth', 'Marker', 'MarkerSize', ...
|
||||
'MarkerEdgeColor', 'MarkerFaceColor', 'DisplayName'};
|
||||
|
||||
% Copy properties from source to target, excluding colors
|
||||
for i = 1:length(propsToCopy)
|
||||
try
|
||||
if ~contains(propsToCopy{i}, 'Color') % Skip color properties
|
||||
targetObj.(propsToCopy{i}) = sourceObj.(propsToCopy{i});
|
||||
end
|
||||
catch
|
||||
% Skip property if it doesn't exist or can't be copied
|
||||
end
|
||||
end
|
||||
end
|
||||
32
Classes/DataBaseHandler/related functions/cleanUpTable.m
Normal file
32
Classes/DataBaseHandler/related functions/cleanUpTable.m
Normal file
@@ -0,0 +1,32 @@
|
||||
function cleanedTable = cleanUpTable(inputTable)
|
||||
% Converts string numbers to numeric, 'NaN' to NaN, and tries to convert date strings to datetime.
|
||||
|
||||
cleanedTable = inputTable;
|
||||
varNames = cleanedTable.Properties.VariableNames;
|
||||
|
||||
for i = 1:numel(varNames)
|
||||
col = cleanedTable.(varNames{i});
|
||||
if iscell(col)
|
||||
numericCol = str2double(col);
|
||||
if all(isnan(numericCol) == strcmpi(col, 'NaN') | cellfun(@isempty, col))
|
||||
cleanedTable.(varNames{i}) = numericCol;
|
||||
else
|
||||
try
|
||||
cleanedTable.(varNames{i}) = datetime(col, 'InputFormat', 'yyyy-MM-dd HH:mm:ss.SSSSSS');
|
||||
catch
|
||||
cleanedTable.(varNames{i}) = string(col);
|
||||
end
|
||||
end
|
||||
elseif isstring(col)
|
||||
numericCol = str2double(col);
|
||||
if all(isnan(numericCol) == strcmpi(col, "NaN"))
|
||||
cleanedTable.(varNames{i}) = numericCol;
|
||||
else
|
||||
try
|
||||
cleanedTable.(varNames{i}) = datetime(col, 'InputFormat', 'yyyy-MM-dd HH:mm:ss');
|
||||
catch
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
53
Classes/DataBaseHandler/related functions/groupIt.m
Normal file
53
Classes/DataBaseHandler/related functions/groupIt.m
Normal file
@@ -0,0 +1,53 @@
|
||||
function resultTable = groupIt(fixedVars, dataTable, aggregationFunction)
|
||||
% groupIt Groups data in a table based on fixedVars and applies aggregationFunction to numeric data.
|
||||
%
|
||||
% resultTable = groupIt(fixedVars, dataTable, aggregationFunction)
|
||||
%
|
||||
% Inputs:
|
||||
% fixedVars - Cell array of variable names to group by
|
||||
% dataTable - Input MATLAB table
|
||||
% aggregationFunction - Function handle (e.g., @mean, @min, @max)
|
||||
%
|
||||
% Output:
|
||||
% resultTable - Grouped and aggregated table
|
||||
|
||||
[G, groupKeys] = findgroups(dataTable(:, fixedVars));
|
||||
varNames = dataTable.Properties.VariableNames;
|
||||
nVars = numel(varNames);
|
||||
aggData = cell(height(groupKeys), nVars);
|
||||
groupCount = zeros(height(groupKeys), 1);
|
||||
|
||||
for i = 1:height(groupKeys)
|
||||
idx = (G == i);
|
||||
groupCount(i) = sum(idx);
|
||||
for j = 1:nVars
|
||||
colData = dataTable.(varNames{j});
|
||||
if isnumeric(colData)
|
||||
if any(idx)
|
||||
aggData{i, j} = aggregationFunction(colData(idx));
|
||||
else
|
||||
aggData{i, j} = NaN;
|
||||
end
|
||||
else
|
||||
if iscell(colData)
|
||||
nonEmptyIdx = find(idx & ~cellfun(@isempty, colData), 1);
|
||||
if ~isempty(nonEmptyIdx)
|
||||
aggData{i, j} = colData{nonEmptyIdx};
|
||||
else
|
||||
aggData{i, j} = [];
|
||||
end
|
||||
else
|
||||
nonEmptyIdx = find(idx, 1);
|
||||
if ~isempty(nonEmptyIdx)
|
||||
aggData{i, j} = colData(nonEmptyIdx);
|
||||
else
|
||||
aggData{i, j} = [];
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
resultTable = cell2table(aggData, 'VariableNames', varNames);
|
||||
resultTable.nRows = groupCount;
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
function [cleanedTable, outliersTable] = removeGroupOutliers(dataTable, fixedVars, y_var)
|
||||
% removeGroupOutliers removes outliers in y_var within each group defined by fixedVars.
|
||||
%
|
||||
% [cleanedTable, outliersTable] = removeGroupOutliers(dataTable, fixedVars, y_var)
|
||||
%
|
||||
% Inputs:
|
||||
% dataTable - Input MATLAB table
|
||||
% fixedVars - Cell array of variable names to group by
|
||||
% y_var - Name of the variable to check for outliers (string or char)
|
||||
%
|
||||
% Outputs:
|
||||
% cleanedTable - Table with outliers removed
|
||||
% outliersTable - Table of removed outlier rows
|
||||
|
||||
[G, groupKeys] = findgroups(dataTable(:, fixedVars));
|
||||
keepIdx = true(height(dataTable), 1);
|
||||
outlierRecords = [];
|
||||
|
||||
for groupIdx = 1:height(groupKeys)
|
||||
groupRows = (G == groupIdx);
|
||||
y_values = dataTable.(y_var)(groupRows);
|
||||
|
||||
% Skip groups with fewer than 3 points
|
||||
if sum(groupRows) < 3
|
||||
continue;
|
||||
end
|
||||
|
||||
% Detect outliers in log10 space (robust for BER, etc.)
|
||||
y_log = log10(y_values);
|
||||
outlierMask = isoutlier(y_log, 'quartiles', 1);
|
||||
|
||||
if any(outlierMask)
|
||||
groupData = dataTable(groupRows, :);
|
||||
outlierGroupTable = groupData(outlierMask, :);
|
||||
|
||||
% Optionally, add group key values for traceability
|
||||
for k = 1:numel(fixedVars)
|
||||
outlierGroupTable.(['Group_', fixedVars{k}]) = repmat(groupKeys{groupIdx, k}, height(outlierGroupTable), 1);
|
||||
end
|
||||
|
||||
outlierRecords = [outlierRecords; outlierGroupTable]; %#ok<AGROW>
|
||||
end
|
||||
|
||||
% Mark outliers for removal
|
||||
groupRowIdx = find(groupRows);
|
||||
keepIdx(groupRowIdx(outlierMask)) = false;
|
||||
end
|
||||
|
||||
cleanedTable = dataTable(keepIdx, :);
|
||||
|
||||
if isempty(outlierRecords)
|
||||
outliersTable = table();
|
||||
else
|
||||
outliersTable = outlierRecords;
|
||||
end
|
||||
|
||||
nRemoved = sum(~keepIdx);
|
||||
nTotalOriginal = height(dataTable);
|
||||
fprintf('Removed %d outliers from the data table (%.2f%% of total %d entries).\n', ...
|
||||
nRemoved, 100*nRemoved/nTotalOriginal, nTotalOriginal);
|
||||
end
|
||||
Reference in New Issue
Block a user