diff --git a/+PlotID/@config/config.m b/+PlotID/@config/config.m new file mode 100644 index 0000000000000000000000000000000000000000..e5366246dc0adb85837b6038864a26f72ec6fbd8 --- /dev/null +++ b/+PlotID/@config/config.m @@ -0,0 +1,78 @@ +classdef config < handle + %CONFIG class handles methoths and attributes related to the config + %file + % Detailed explanation goes here + % handle class used for dynamicy property updates + + properties (SetAccess = private) + mandatoryFields = {'Author', 'ProjectID'} + optionFields + end + + properties (SetAccess = protected) + configData + configFileName + end + + methods + function obj = config(configFileName) + %CONFIG Construct an instance of this class + % Detailed explanation goes here + obj.configFileName = configFileName; + try + txt = fileread(obj.configFileName); + obj.configData = jsondecode(txt); + assert(checkConfig(obj)); + + catch + msg = ['no valid config File with the filename ',... + obj.configFileName, ' found.' newline,... + 'Please enter the required information manually']; + disp(msg); + obj.configData = obj.wizard('initilise'); + m = input('Do you want to save the config, Y/N [Y]:','s'); + if ismember(m,{'Y','y'}) + obj.writeConfig(fullfile(pwd)); + end + end%try + + end + + function outputArg = checkConfig(obj) + %checkConfig validates the config file + % 1. Check if mandatory Fields are set + check = isfield(obj.configData,obj.mandatoryFields); + outputArg = all(check); + + end + + function writeConfig(obj,path) + %writeConfig writes the config file to path + % TODo; + fid = fopen(fullfile(path,obj.configFileName),'w'); + txt = jsonencode(obj.configData,'PrettyPrint',true); + %fprintf does not write paths correctly !!! + fwrite(fid,txt); + fclose(fid); + end + + function configData = addPublishOptions(obj,mode) + %writeConfig writes the config file to path + % TODo; + obj.configData.options = obj.plotID_options(mode); + end + + function outputArg = method1(obj,inputArg) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + outputArg = obj.Property1 + inputArg; + end + + end + + methods (Static) + configStruct = wizard(mode) + optionStruct = plotID_options(input) + end +end + diff --git a/+PlotID/@config/plotID_options.m b/+PlotID/@config/plotID_options.m new file mode 100644 index 0000000000000000000000000000000000000000..235f1cb791ef0484873d32a2883aae5daf387406 --- /dev/null +++ b/+PlotID/@config/plotID_options.m @@ -0,0 +1,28 @@ +function [options] = plotID_options(input) +%PLOTID_OPTIONS Summary of this function goes here +% Detailed explanation goes here + +options = struct(); +switch input + case 'default' %same as Arguments Block + options.Location = 'local'; % storage path + options.Method = 'individual'; + options.ParentFolder = 'export'; + options.ConfigFileName = 'config.json';%individual config names possible + options.CopyUserFCN = true; + options.CSV = false; + options.ShowMessages = true; + options.ForcePublish = false; %publish anyway + case 'debug' + options.Location = 'local'; % storage path + options.Method = 'individual'; + options.ParentFolder = 'export'; + options.ConfigFileName = 'config.json';%individual config names possible + options.CopyUserFCN = true; + options.CSV = true; + options.ShowMessages = true; + options.ForcePublish = true; %publish anyway +end + +end + diff --git a/+PlotID/@config/wizard.m b/+PlotID/@config/wizard.m new file mode 100644 index 0000000000000000000000000000000000000000..de66181d95c25c8d3efa4d06e2e693c28f8bdfbc --- /dev/null +++ b/+PlotID/@config/wizard.m @@ -0,0 +1,39 @@ +function config = wizard(mode) +%wizard creates config files depending on the selected mode +% initilise ask only for the input that is neccessary to run plotID + +config = struct(); +switch mode + case 'initilise' + config.Author = inputRequired('your Name'); + config.ProjectID = inputRequired('your Project Number'); + + msg = ['Do you want to add a remote path?' newline,... + 'Otherwise your files will be stored locally.' newline,... + 'You can add this later by rerunning the initilisation.']; + disp(msg); + m = input('Do you want add a remothe path, Y/N [Y]:','s'); + + if ismember(m,{'Y','y'}) + config.ServerPath = uigetdir(); + end + otherwise + error('wizard mode undefined in CLASS config'); +end %switch + +end + +function usrTxt = inputRequired(msg) +%Input Dialog that repeats if left empty +inputRequired = true; +while inputRequired + prompt = ['Please enter ', msg , ':']; + inpt = input(prompt,'s'); + if isempty(inpt) + disp('Input must not be empty!'); + else + usrTxt = inpt; + inputRequired = false; + end +end +end \ No newline at end of file diff --git a/+PlotID/@userDLG/userDLG.m b/+PlotID/@userDLG/userDLG.m new file mode 100644 index 0000000000000000000000000000000000000000..3ec2b4b9f2af8d2833631b67a52cf9d0f1c1a8a0 --- /dev/null +++ b/+PlotID/@userDLG/userDLG.m @@ -0,0 +1,80 @@ +classdef userDLG + %userDLG the userDLG Class is responsible for the user dialog + % error handeling, user messages and warnings will be provided from + % this class, depending on the user options and error type. + % This reduces messaging overhead and code length in PlotID.Publish() + + properties + configError = false + scriptPublished = false + rdFilesPublished = false + figurePublished = false + msg = '' + end + + properties (SetAccess = protected) + success = false + showMessages {mustBeNumericOrLogical} + forcePublish {mustBeNumericOrLogical} + ID %plotID + end + + methods + function obj = userDLG(ID,options) + %CATCHERROR Construct an instance of this class + if isempty(ID) + ID = ''; + end + obj.ID = ID; + obj.showMessages = options.ShowMessages; + obj.forcePublish = options.ForcePublish; + end + + function output = get.success(obj) + % checks if all publish operations are true + obj.success = all([obj.scriptPublished; obj.rdFilesPublished;... + obj.figurePublished]); + output = obj.success; + end + + function obj = set.msg(obj,message) + %setmsg Summary of this method goes here + % Detailed explanation goes here + obj.msg = message; + end + + function [] = userMSG(obj,message) + %userMSG user message without priority + % MSG will only be displaye if ShowMessages is true + if obj.showMessages + disp(message); + end + end + + function [] = error(obj,message) + %error handles critical errors + % critical errors will always result in a matlab error + + %ToDO: Add function(s) before termination + error(message); + end + + function [] = softError(obj,message) + %softError handles soft errors + % soft errors can be changed to warning if ForcePublish is + % selected by the user + if obj.forcePublish + warning(message); + else + error(message); + end + end + + function outputArg = method1(obj,inputArg) + %METHOD1 Summary of this method goes here + % Detailed explanation goes here + outputArg = obj.Property1 + inputArg; + end + end +end + diff --git a/+PlotID/Publish.m b/+PlotID/Publish.m index 79ecc2b1799132e3890d2924bf90ec8b46a64d01..231c185ab862f4e32daa5c97a263f1a49a5c1e02 100644 --- a/+PlotID/Publish.m +++ b/+PlotID/Publish.m @@ -1,5 +1,13 @@ -function Publish(DataPaths, ID, figure, options) -%Publishes saves plot, data and measuring script +function Publish(DataPaths,scriptPath, figure, options) +%%Publish(DataPaths,scriptPath, ID, figure, options) saves plot, data and measuring script +% +% DataPaths contains the path(s) to the research data, for multiple files +% use a cell array +% scriptPath contains the path to the script, this can be provided with +% the simple call of scriptPath = [mfilename('fullpath'),'.m'], note that +% the extension is mandatory +% +% Options: % Location sets the storage location. 'local' sets the storage location % to the current folder (an export folder will be created), 'server' is a % remote path, that is defined in the config file. @@ -10,14 +18,31 @@ function Publish(DataPaths, ID, figure, options) % path is used, PlotId will use this path a storagePath arguments - DataPaths - ID (1,:) {mustBeNonzeroLengthText} % ID must be provided + DataPaths {mustBeDataPath} % location of the data-file(s) + scriptPath (1,:) {mustBeFile} % location of the matlab script figure (1,:) {mustBeFigure} % Checks if figure is a figure object options.Location {mustBeMember(options.Location ,{'local','server','manual','CI-Test'})} = 'local' % storage path options.Method {mustBeMember(options.Method ,{'individual','centralized'})} = 'individual' options.ParentFolder (1,:) {mustBeText} = 'export' - options.CopyUserFCN (1,1) {mustBeNumericOrLogical} = true + options.ConfigFileName (1,:) {mustBeText} = 'config.json' %individual config names possible + options.CopyUserFCN (1,1) {mustBeNumericOrLogical} = true options.CSV (1,1) {mustBeNumericOrLogical} = false + options.ShowMessages(1,1) {mustBeNumericOrLogical} = true + options.ForcePublish (1,1) {mustBeNumericOrLogical} = false %publish anyway + +end + +%% argument validation +%catch string and non cell inputs in DataPaths +if ~iscell(DataPaths) + DataPaths = {DataPaths}; %Cell array +end + +for i=1:numel(DataPaths) + if isstring(DataPaths{i}) + % strings will cause problems + DataPaths{i} = char(DataPaths{i}); + end end %catch multiple figures in fig @@ -29,18 +54,31 @@ if numel(figure) > 1 warning(msg); end -%% read config file -try - txt = fileread('config.json'); - config = jsondecode(txt); - configError = false; -catch - msg = ['Error while reading the config file' newline,... - ' publishing on server not possible']; +%get ID from Figure +ID = figure.Tag; + +if isempty(ID) + % no ID found, User dialog for Folder name + ID = inputdlg(['No ID defined- ' newline,... + 'Please enter a folder name to continue:'],'Please enter a folder name'); + ID = ID{1}; + msg = ['No ID found - consider to use the TagPlot function before ',... + 'you publish ', newline, 'your files will be stored in ' , ID]; warning(msg); - configError = true; end +%% read config file +% there is only one config Object (handleClass) +configObj = PlotID.config(options.ConfigFileName); +if isfield(configObj.configData, 'options') + % is working but prune to user errors + options = configObj.configData.options; +end + +% Error and MSG handeling +dlgObj = PlotID.userDLG(ID,options); + + %% storage location switch options.Location case 'local' @@ -48,35 +86,43 @@ switch options.Location storPath = options.ParentFolder; else % use the script path as export path - scriptPath = fileparts(DataPaths.script); - storPath = fullfile(scriptPath,options.ParentFolder); + scriptLocation = fileparts(scriptPath); + storPath = fullfile(scriptLocation,options.ParentFolder); end case 'server' %from config File - storPath = config.ServerPath; + if dlgObj.configError + msg = ['Error while reading the config file' newline,... + ' publishing on server not possible']; + dlgObj.error(msg); + end + storPath = config.ServerPath; case 'manual' %UI storPath = uigetdir(); case 'CI-Test' storPath = fullfile(pwd,'CI_files',options.ParentFolder); end -folderName = char(ID); -%% Create Data-Directory +folderName = ['.',char(ID)]; %hidden folder + +%% Create data directory if isfolder(fullfile(storPath,folderName)) - error(['Folder ',folderName, ' exists - Plot was already published ']); + dlgObj.error(['Folder ',folderName, ' exists - Plot was already published ']); elseif mkdir(fullfile(storPath,folderName)) else - error('Directory could not be created - check remote path and permissions'); + dlgObj.error('Directory could not be created - check remote path and permissions'); end disp(['publishing of ', ID, ' started']); -%% Create a Copy of the script and user functions(optional) +%% Create a copy of the script and user functions(optional) % script -PlotID.createFileCopy({[DataPaths.script,'.m']},folderName,storPath,ID, 'script'); +[~, status, msg] = PlotID.createFileCopy({scriptPath},folderName,storPath,ID, 'script'); +dlgObj.scriptPublished =status; +dlgObj.userMSG(msg); % user functions -[fList,pList] = matlab.codetools.requiredFilesAndProducts(DataPaths.script); +[fList,pList] = matlab.codetools.requiredFilesAndProducts(scriptPath); if options.CopyUserFCN - fList = fList(~ismember(fList,[DataPaths.script,'.m'])); % rmv script from list + fList = fList(~ismember(fList,scriptPath)); % rmv script from list fList = fList(~contains(fList,'config.json')); % rmv config.json from list fList = PlotID.removePltIdFiles(fList); % Do not copy files that are part of plot ID if ~isempty(fList) @@ -96,16 +142,16 @@ switch options.Method currentPath = fullfile(storPath); %list all files - fList = dir(fullfile(storPath,DataFolderName, '**\*.*')); + fList = dir(fullfile(storPath,DataFolderName, ['**',filesep,'*.*'])); %get list of files and folders in any subfolder fList = fList(~[fList.isdir]); %remove folders from list fList = struct2table(fList); % Check if the new plot is based on the original data-set % copy the data(once) - for i=1:numel(DataPaths.rdata) + for i=1:numel(DataPaths) % check if identical file exists (status = 1) - [~, idx] = PlotID.fileCompare(DataPaths.rdata{i},fList); + [~, idx] = PlotID.fileCompare(DataPaths{i},fList); % create Linked HDF5 files for identical files if any(idx) fList.path = fullfile(fList.folder,fList.name); @@ -113,78 +159,118 @@ switch options.Method relativeSourcePath = strrep(sourcePath,currentPath,''); if contains(sourcePath,{'.h5','.hdf5'}) % Linking only for HDF5 - PlotID.createLinkedHDF5(relativeSourcePath{1,1},storPath,ID); + linkedHDFPath = fullfile(storPath,folderName); + PlotID.createLinkedHDF5(relativeSourcePath{1,1},linkedHDFPath); end else % no identical file exists %Copy the file in data and create the links (if hdf5) - [dataPath] = PlotID.createFileCopy(DataPaths.rdata{i},'data',storPath,ID,'dataCentral'); - relativeDataPath = strrep(dataPath,currentPath,''); + [dataPath, status, msg] = PlotID.createFileCopy(DataPaths{i},'data',storPath,ID,'dataCentral'); + pathToData = strrep(dataPath,currentPath,''); %WIP - if contains(DataPaths.rdata{i},{'.h5','.hdf5'}) % Linking only for HDF5 + if contains(DataPaths{i},{'.h5','.hdf5'}) % Linking only for HDF5 % and create also linked files in the plot folder - PlotID.createLinkedHDF5(relativeDataPath,storPath,ID); + linkedHDFPath = fullfile(storPath,folderName); + [status] = PlotID.createLinkedHDF5(pathToData,linkedHDFPath); end %if end %if end %for clear DataFolderName case 'individual' % Create a copy of the research data - PlotID.createFileCopy(DataPaths.rdata,folderName,storPath,ID, 'data'); + [~, status, msg] = PlotID.createFileCopy(DataPaths,folderName,storPath,ID, 'data'); end +%temporary: +dlgObj.rdFilesPublished = status; +dlgObj.userMSG(msg); + %% Write Config File +exportPath = fullfile(storPath,folderName); +configObj.writeConfig(exportPath); -if ~configError %config File must exist - % copy config file - configPath = PlotID.createFileCopy('config.json',folderName,... - storPath,ID, 'data'); -else - configPath = fullfile(storPath,folderName,'config.json'); - config = struct(); - if ispc - config.author = getenv('USERNAME'); - end -end % add further Metadata -config.ProjectID = ID; -config.CreationDate = datestr(now); -config.MatlabVersion = version; -config.ToolboxVersions = pList; - -%write config -fid = fopen(char(configPath),'w'); -txt = jsonencode(config,'PrettyPrint',true); +meta =struct(); +if ispc + meta.author = getenv('USERNAME'); +end +meta.ProjectID = ID; +meta.CreationDate = datestr(now); +meta.MatlabVersion = version; +meta.ToolboxVersions = pList; + +% write meta +metaPath = fullfile(storPath,folderName,'plotID_data.json'); +fid = fopen(char(metaPath),'w'); +txt = jsonencode(meta,'PrettyPrint',true); fprintf(fid,txt); fclose(fid); %% Export the Plot try - PlotName = [ID,'_plot']; % plotname - RemotePath = fullfile(storPath ,folderName, PlotName); - % Matlab figure - savefig(figure,RemotePath); - % the png should only be a preview - exportgraphics(figure,[RemotePath,'.png'],'Resolution',300); + PlotName = [ID,'_plot']; % plotname + RemotePath = fullfile(storPath ,folderName, PlotName); + % Matlab figure + savefig(figure,RemotePath); + % the png should only be a preview + exportgraphics(figure,[RemotePath,'.png'],'Resolution',300); + dlgObj.figurePublished = true; catch - warning('Plot export was not successful') + dlgObj.softError('Plot export was not successful'); end -disp(['publishing of ', ID , ' done']); - % CSV EXport if options.CSV T = table(); - T.research_Data = DataPaths.rdata'; T.PlotID(:) = {ID}; - T.Script_Name(:) = {[DataPaths.script,'.m']}; + T.research_Data = DataPaths'; T.PlotID(:) = {ID}; + T.Script_Name(:) = {scriptPath}; T.Storage_Location(:) = {storPath}; T.Date(:) = {datestr(now)}; T = movevars(T,'PlotID','before',1); writetable(T, fullfile(storPath, 'overview_table.csv'),'WriteMode','append'); end +%% final renaming and error/warning handeling +% if no error orcurred or if force publish is activated, rename the hidden +% folder to a non hidden one, otherwise delete it. +if dlgObj.success || options.ForcePublish + oldPath = fullfile(storPath,folderName); + newPath = strrep(oldPath,'.',''); %remov dot + status = movefile(oldPath,newPath); %rename directory +else + % error from userDlg class! +end + +if status + disp(['publishing of ', ID , ' done']); %always displayed onsucess +else % publish was not sucessfull! + %replace with error from userDLG Class + dlgObj.error(['publishing of ', ID , ' failed']) +end + + end %function +%% Argument Validation Functions function tf = mustBeFigure(h) %checks if input is a figure object tf = strcmp(get(h, 'type'), 'figure') & isa(h, 'matlab.ui.Figure'); end + +function mustBeDataPath(DataPaths) +%checks if input is a valid DataPath object + if ~iscell(DataPaths) + DataPaths = {DataPaths}; + end + tf = zeros(1,numel(DataPaths)); + + for i=1:numel(DataPaths) + tf(i) = ~isfile(DataPaths{i}); + end + + if any(tf) + eidType = 'mustBeDataPath:notaValidFilePath'; + msgType = 'DataPaths must contain file-path(s)'; + throwAsCaller(MException(eidType,msgType)) + end +end + diff --git a/+PlotID/TagPlot.m b/+PlotID/TagPlot.m index 4a5965803f3f57eb5de1da8fce1ed84d070c5a0e..b925379f4a5a8a23f5b37d802d0edc61eda0bf4f 100644 --- a/+PlotID/TagPlot.m +++ b/+PlotID/TagPlot.m @@ -14,43 +14,42 @@ function [figs, IDs] = TagPlot(figs, options) arguments figs (1,:) {mustBeFigure} options.ProjectID (1,:) {mustBeText}= '' - options.Fontsize (1,1) {mustBeInteger} = 8 + options.Fontsize (1,1) {mustBeInteger} = 8 + options.Color (1,3) {mustBeNonnegative} = 0.65*[1 1 1] % grey options.Location (1,:) {mustBeText} = 'east' + options.DistanceToAxis {mustBeReal} = .01 % relative distance options.Position (1,2) {mustBeVector} = [1,0.4] % default for east - options.Rotation (1,1) {mustBeInteger} = 0 + options.Rotation (1,1) {mustBeReal} = NaN + options.ConfigFileName (1,:) {mustBeText} = 'config.json' end if isempty(options.ProjectID) - try - txt = fileread('config.json'); - config = jsondecode(txt); - catch - config =struct; - config.ProjectID = ''; - warning("No ProjectID was definded and no config.json could be found"); - - end - - if ~isempty(config.ProjectID) - options.ProjectID = config.ProjectID; - else - warning('no project options.ProjectID defined') - end + configObj = PlotID.config(options.ConfigFileName); + configData = configObj.configData; + options.ProjectID = configData.ProjectID; end switch options.Location case 'north' - options.Position = [0.4,0.95]; - options.Rotation = 0; + y = 1 - options.DistanceToAxis + options.Position = [0.4,y]; + Rotation = 0; case 'east' - options.Position = [1,0.4]; - options.Rotation = 90; + x = 1 - options.DistanceToAxis; + options.Position = [x,0.4]; + Rotation = 90; case 'south' - options.Position = [0.4,.02]; - options.Rotation = 0; + y = 0 + options.DistanceToAxis; + options.Position = [0.4,y]; + Rotation = 0; case 'west' - options.Position = [.05,0.4]; - options.Rotation = 90; + x = 0 + options.DistanceToAxis; + options.Position = [x,0.4]; + Rotation = 90; + case 'southeast' + y = 0 + options.DistanceToAxis; + options.Position = [0.8, y]; + Rotation = 0; case 'custom' % Check if Position is valid if ~all(0 <= options.Position & options.Position <= 1) @@ -62,6 +61,10 @@ switch options.Location options.Location = 'east'; options.Position = [1,0.4]; end +if ~isnan(options.Rotation) + Rotation = options.Rotation; +end + IDs = cell(numel(figs),1); for n = 1:numel(figs) @@ -72,10 +75,11 @@ for n = 1:numel(figs) ylim =get(axes,'YLim'); xlim =get(axes,'XLim'); %ID - position = [options.Position(1)*xlim(2), options.Position(2)*ylim(2)]; + + position = [options.Position(1), options.Position(2)]; text(axes,position(1),position(2), IDs{n},'Fontsize',options.Fontsize,... - 'Rotation',options.Rotation, 'VerticalAlignment','bottom','Color',... - 0.65*[1 1 1],'BackgroundColor','w'); + 'Rotation',Rotation, 'VerticalAlignment','bottom','Color',... + options.Color,'BackgroundColor','w', 'Units', 'normalized'); set(figs(n),'Tag', IDs{n}); end diff --git a/+PlotID/createFileCopy.m b/+PlotID/createFileCopy.m index 061f10903343ccde4ea33ea666f93b19f414cb79..03e394c2000633f015a48a9340889275dec54549 100644 --- a/+PlotID/createFileCopy.m +++ b/+PlotID/createFileCopy.m @@ -1,4 +1,4 @@ -function [storagePaths] = createFileCopy(filePaths,folderName,storPath,ID,type) +function [storagePaths, status, msg] = createFileCopy(filePaths,folderName,storPath,ID,type) % Creates a copy of the files (can be used for multiple paths in a cell array) % folderName is the name of the exporting folder % returns the storage paths were files were stored @@ -68,8 +68,10 @@ try copyfile(FileNameAndLocation,RemotePath); storagePaths{i} = RemotePath; end - disp([type, ' sucessfully published']); + status = true; + msg =([type, ' successfully published']); catch + status = false; warning([type,' export was not sucessful']) if exist('errorMSG') error(errorMSG); diff --git a/+PlotID/createLinkedHDF5.m b/+PlotID/createLinkedHDF5.m index 8fecfaed42b86014895c16a9bdd9a03eb554bec5..8807977d929aed8af169a866c4ba4aa329fda8d4 100644 --- a/+PlotID/createLinkedHDF5.m +++ b/+PlotID/createLinkedHDF5.m @@ -1,4 +1,4 @@ -function [status] = createLinkedHDF5(SourceFile,TargetPath,ID) +function [status] = createLinkedHDF5(SourceFile,TargetPath) %createLinkedHDF5 creates a HDF file that references the Sourcefile % TargetPath is the storage location, ID the foldername % Status returns true if the function was sucessfull @@ -16,12 +16,12 @@ end %old %fid = H5F.create(fullfile(TargetPath,ID,[ID,'_data.h5'])); - fid = H5F.create(fullfile(TargetPath,ID,[filename,ext])); + fid = H5F.create(fullfile(TargetPath,[filename,ext])); %create External Link to Sourcefile in the Group linkToExternal - H5L.create_external(['..\',SourceFile],'/',fid, SourceFile ,plist_id,plist_id); - %H5L.create_external(['..\data\',SourceFile],'/',fid, SourceFile ,plist_id,plist_id); %original + H5L.create_external(['..',SourceFile],'/',fid, SourceFile ,plist_id,plist_id); + %H5L.create_external(['..',filesep,'data',filesep,SourceFile],'/',fid, SourceFile ,plist_id,plist_id); %original H5F.close(fid); - disp([fullfile(TargetPath,ID,[filename,ext]),' created']); + disp([fullfile(TargetPath,[filename,ext]),' created']); status = 1; % catch % warning('No linked HDF file was created'); diff --git a/+PlotID/initilise.m b/+PlotID/initilise.m new file mode 100644 index 0000000000000000000000000000000000000000..4f893441299d085fa8f5a06fbd4dc36b105df04c --- /dev/null +++ b/+PlotID/initilise.m @@ -0,0 +1,7 @@ +function [status] = initilise() +%INITILISE Initilisation for first time úsage + + + +end + diff --git a/+PlotID/removePltIdFiles.m b/+PlotID/removePltIdFiles.m index 37ef1827ad2049bd261780e4d190f05907a7b676..169990d0f7e6f6d99b73f1b5d91c8454b9cf9a65 100644 --- a/+PlotID/removePltIdFiles.m +++ b/+PlotID/removePltIdFiles.m @@ -1,19 +1,26 @@ function [fListClean] = removePltIdFiles(fList) %removePltIdFiles removes functions that are part of PlotID out of flist % Detailed explanation goes here -%addpath('..\fcn_core'); [~,names,ext] = fileparts(fList); names = strcat(names, ext); % add ext for comparison % Get a list of all .m files that are part of Plot id -PltID_flist = struct2table(dir('+PlotID')); %get list of files -[~,~,PltID_flist.ext(:)] = fileparts(PltID_flist.name(:)); % add ext column - -PltID_flist = PltID_flist(strcmp(PltID_flist.ext,'.m'),:); +packageContent = what('PlotID'); +% packageContent.classes has no extensions +PltID_classlist = packageContent.classes; +% Class Methods need to be listed in an additional function +Class_flist = cell(1,numel(packageContent.classes)); %preallocate +for i=1:numel(packageContent.classes) + temp = what(['PlotID',filesep,'@',PltID_classlist{i}]); + Class_flist{i} = temp.m; +end + +Class_flist = vertcat(Class_flist{:}); +% all plotID .m files: +PltID_flist = [packageContent.m; Class_flist]; % Comparison and filter -fListClean = fList(~ismember(names,PltID_flist.name)); - +fListClean = fList(~ismember(names,PltID_flist)); end diff --git a/.gitignore b/.gitignore index 708bea6b2032d70640c74a197d183a115ff0bdda..32ea2493881e83b59a61c69dbd6ba01281a72d3b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ testdata_2.h5 testdata2.h5 test_data.mat export/* +unused*/* # Octave session info octave-workspace diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7901609ef3b830a97e409b23c51890ace703b737..938d8bc80d5bdeff95757803ef2d80a778269d1e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,16 @@ Test Code: stage: Run tags: - matlab + - bash script: - - ./CI_files/runtest.ps1 - - cat log2.txt + - cd ./CI_files + - chmod +x ./runner_linux.sh + - ./runner_linux.sh + - cat ./log.txt + artifacts: + paths: + - ./CI_files/log.txt + - ./CI_files/matlab_log.txt + + when: always + expire_in: 1 week diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000000000000000000000000000000000..6c8e17a380a65867c2de3888abcc23f8d09d4f8f --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,18 @@ +cff-version: 1.2.0 +title: >- + plotID a toolkit for connecting research data and + figures +message: >- + developed at the Chair of Fluid System, Techische + Universität Darmstadt within the framework of the NFDI4Ing consortium Funded by the German Research Foundation (DFG) - project number 442146713 +type: software +authors: + - given-names: Jan + family-names: Lemmer + email: jan.lemmer@fst.tu-darmstadt.de + affiliation: 'Chair of Fluid Systems, TU Darmstadt' + orcid: 'https://orcid.org/0000-0002-0638-1567' + - given-names: Martin + family-names: Hock + email: martin.hock@fst.tu-darmstadt.de + affiliation: 'Chair of Fluid Systems, TU Darmstadt' diff --git a/CI_files/default_test.m b/CI_files/default_test.m index 505c360ea8fde4390434772eeece053120d15f0e..f6c7309d399deaa2453cf51045d5970073da8ba8 100644 --- a/CI_files/default_test.m +++ b/CI_files/default_test.m @@ -1,72 +1,126 @@ function [result] = default_test() -%UNTITLED2 This is a simple test if Plot ID works for the default settings +%default_test() This is a simple test if Plot ID works for the default settings % Detailed explanation goes here clear; clc; close all; +% set path +% starting path of gitlab runner is in CI_files +if contains(pwd,'CI_files') + cd .. % move one directory up +end + +addpath('CI_files'); + % clean up, if previous run failed try - delete CI_files/export/* CI_files/*.mat CI_files/*.h5 - rmdir('CI_files/export','s'); + delete(['CI_files' filesep 'export' filesep '*']); + delete(['CI_files' filesep '*.mat']); + delete(['CI_files' filesep '*.h5']); + rmdir(['CI_files' filesep 'export'],'s'); end -ProjectID = 'Test01'; -%% Data -% some random data -x = linspace(0,7); -y = rand(1,100)+2; -dataset1 = 'test_data.mat'; -save('CI_files/test_data.mat','x','y'); -% some data as .h5 -x1 = linspace(0,2*pi); -y1 = sin(x1)+2; - -% define file path & name -fpath = "CI_files/testdata_2.h5"; -dataset2 = 'testdata_2.h5'; - -% create hdf5 file and dataset > write data to hdf5 file / dataset -h5create(fpath, "/x1", size(x1), "Datatype", class(x1)) -h5create(fpath, "/y1", size(y1), "Datatype", class(y1)) -h5write(fpath, "/x1", x1) -h5write(fpath, "/y1", y1) - -%% Plotting - -fig(1) =figure('visible','off'); -plot(x,y,'-k'); -hold on -plot(x1,y1,'-r'); +% initialise +numberOfTests = 2; +testResults = zeros(numberOfTests,1); + +%start log +fid = fopen(fullfile('CI_files','log.txt'),'w'); +txt = ['default test started ' newline]; +fprintf(fid,txt); + +% create Config for CI-Tests +fid1 = fopen(fullfile('CI_files','CI_config.json'),'w'); +configData.Author = 'CI-Test'; configData.ProjectID = 'CI-001'; +txt = jsonencode(configData,'PrettyPrint',true); +%fprintf does not write paths correctly !!! +fwrite(fid1,txt); +fclose(fid1); + +try + ProjectID = 'Test01'; + %% Data + % some random data + x = linspace(0,7); + y = rand(1,100)+2; + dataset1 = fullfile('CI_files','test_data.mat'); + save(dataset1,'x','y'); + % some data as .h5 + x1 = linspace(0,2*pi); + y1 = sin(x1)+2; + + % define file path & name + fpath = fullfile("CI_files","testdata2.h5"); + dataset2 = fullfile('CI_files','testdata2.h5'); + + % create hdf5 file and dataset > write data to hdf5 file / dataset + h5create(fpath, "/x1", size(x1), "Datatype", class(x1)) + h5create(fpath, "/y1", size(y1), "Datatype", class(y1)) + h5write(fpath, "/x1", x1) + h5write(fpath, "/y1", y1) + + %% Plotting + + fig(1) =figure('visible','off'); + plot(x,y,'-k'); + hold on + plot(x1,y1,'-r'); + msg = 'simple_test succeed stage 1'; +catch + testResults(1) = 1; %test fails + msg = 'simple_test failed in Stage 1'; + warning(msg); +end + +fprintf(fid,[msg newline]); %% Tag the plot try - [figs, ID] = PlotID.TagPlot(fig,'ProjectID', ProjectID); + [figs, ID] = PlotID.TagPlot(fig,'ConfigFileName', 'CI_config.json'); %% call a dummy function - a=1; - a = example_fcn(a); + %a=1; + %a = example_fcn(a); %% publishing % The functions needs the file location, the location of the data and the % figure - path.script = mfilename('fullpath'); % filename of the m.script + script = [mfilename('fullpath'),'.m']; % filename of the m.script % file name of the data - path.rdata = {dataset1,dataset2} ; % don't forget the extension + rdata = {dataset1,dataset2} ; % don't forget the extension - PlotID.Publish(path, ID, figs, 'Location', 'CI-Test') - result = true; + PlotID.Publish(rdata,script, figs, 'Location', 'CI-Test',... + 'ConfigFileName', 'CI_config.json'); - % clean up - delete CI_files/export/* CI_files/*.mat CI_files/*.h5 - rmdir('CI_files/export','s'); - - clear; clc; - + msg = 'simple_test succeed Stage 2'; + catch - result = false; - warning('simple_test failed'); + testResults(2) = 1; %fail + msg = 'simple_test failed in Stage 2'; + warning(msg); +end +fprintf(fid,[msg newline]); %log + +%% Test result +if any(testResults) + result = 1; %fail + warning('test failed'); +else + result = 0; % pass + disp('test succeed'); +end + +fclose(fid); + +% final clean up +try + delete(['CI_files' filesep 'export' filesep '*']); + delete(['CI_files' filesep '*.mat']); + delete(['CI_files' filesep '*.h5']); + rmdir(['CI_files' filesep 'export'],'s'); + delete(fullfile('CI_files','CI_config.json')); end end diff --git a/CI_files/fail_runner.m b/CI_files/fail_runner.m new file mode 100644 index 0000000000000000000000000000000000000000..e9071dd65411802d16b925aca9ca35c5df933ea4 --- /dev/null +++ b/CI_files/fail_runner.m @@ -0,0 +1,8 @@ +function [retVal] = fail_runner() +%RUNNER_TESTING testing function to test, if the runner is set up properly + retVal = 1; % test should fail ! + fid = fopen(fullfile('log.txt'),'w'); + txt = ['This is a test, Errorstate: ', num2str(retVal)]; + fprintf(fid,txt); + fclose(fid); +end diff --git a/CI_files/pass_runner.m b/CI_files/pass_runner.m new file mode 100644 index 0000000000000000000000000000000000000000..957268576f9bd1f4bdc89fb2cbcb69428d8cd229 --- /dev/null +++ b/CI_files/pass_runner.m @@ -0,0 +1,8 @@ +function [retVal] = pass_runner() +%pass_runner testing function to test, if the runner is set up properly + retVal = 0; % test should succeed ! + fid = fopen(fullfile('log.txt'),'w'); + txt = ['This is a test, Errorstate: ', num2str(retVal)]; + fprintf(fid,txt); + fclose(fid); +end diff --git a/CI_files/runner_linux.sh b/CI_files/runner_linux.sh new file mode 100644 index 0000000000000000000000000000000000000000..f64784cf471314482b52f05ff6f80a39067a367a --- /dev/null +++ b/CI_files/runner_linux.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +#matlab -r "disp(['Current folder: ' pwd])" +matlab $@ -nodisplay -nodesktop -nosplash -logfile matlab_log.txt -r "default_test;exit(ans);" + +exitstatus=$? +if [[ $exitstatus -eq '0' ]] +then + echo "matlab succeed. Exitstatus: $exitstatus" + exit $exitstatus +else + echo "matlab failed. Exitstatus: $exitstatus" + exit $exitstatus + +fi diff --git a/CI_files/runner_test.m b/CI_files/runner_test.m deleted file mode 100644 index 496afd8558f128e08826f25e62472d6b3cb15f62..0000000000000000000000000000000000000000 --- a/CI_files/runner_test.m +++ /dev/null @@ -1,7 +0,0 @@ -function [result] = runner_test() -%RUNNER_TEST testing function to test, if the runner is set up properly -result = true; - -exit(result); -end - diff --git a/CI_files/runtest.ps1 b/CI_files/runtest.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..15d17374764ae708af434eabcfa372828f527be0 --- /dev/null +++ b/CI_files/runtest.ps1 @@ -0,0 +1,17 @@ +# runtest.ps1 + +$LOGFILE='CI_log.txt' +$wd = pwd; +$LOGFILE = $("$wd" + "\" + "$LOGFILE") +$CIfolder = "$wd" + "\" + "CI_files\" +# $MFILE='"C:\git\NFDI4ing\plot_ID_matlab\CI_files\runner_test.m"' +$arguments = "-nodesktop", "-nosplash","-minimize","-wait","-sd", "$wd", "-logfile", "$LOGFILE","-batch","CI_files\test_runner_STFS.m" +$Returnvalue = & 'C:\Program Files\MATLAB\R2021b\bin\matlab.exe' $arguments + +#$CODE = $? +# Returnvalue doesnt get anything useful from matlab +# Write-Output($Returnvalue) +Write-Output(Get-Content($LOGFILE)) + +exit $CODE + diff --git a/Initialisation.m b/Initialisation.m new file mode 100644 index 0000000000000000000000000000000000000000..97835f3364ac842e857a021953fa2f2c632fd38e --- /dev/null +++ b/Initialisation.m @@ -0,0 +1,29 @@ +%% This is the PlotID Initialisation file +% Please run this script before using PlotID + +%% Step 1: Add plotID location to you MATLAB path +% This is necessary to use PlotID in your personal MATLAB Scripts +addpath(pwd); +savepath; + +%% Section 2 Config File +% To make things easy, you should use the config file. The following wizard +% will help you create one: +writeConfig = true; + +% Ask User if current config should be overwritten +if isfile('config.json') + m = input('Do you want to overwrite the current config file, Y/N [Y]:','s'); + writeConfig = ismember(m,{'Y','y'}); +end + +if writeConfig + config = PlotID.config.wizard('initilise'); + fid = fopen('config.json','w'); + txt = jsonencode(config,'PrettyPrint',true); + fwrite(fid,txt); + fclose(fid); +end + +%% +disp('Initialisition done.') diff --git a/PlotID_Demo.m b/PlotID_Demo.m deleted file mode 100644 index 766ad17cfb60feb31820c5382e61b6f3cc7d413b..0000000000000000000000000000000000000000 --- a/PlotID_Demo.m +++ /dev/null @@ -1,74 +0,0 @@ -%% Example Script -% This Script is meant to demonstrate the capabilities of the PlotID tool. - -%% Clear Environment -clear; clc; close all; -addpath('CI_files'); % Test scripts -try - delete testdata2.h5; -end - -%% Set ProjectID -% ProjectID can also be set in the config file -% Leave empty for using the ID from the config file -ProjectID = 'FST01'; - -%% Data -% Creating Random Data to use as data-file -x = linspace(0,7); -y = rand(1,100)+2; -dataset1 = 'test_data.mat'; -% use absolute paths for good practise -dataset1 = fullfile(pwd, dataset1); -save(dataset1,'x','y'); - -% some data as .h5 -x1 = linspace(0,2*pi); -y1 = sin(x1)+.5*sin(2*x1)+2; - -% define file path & name - -fpath = fullfile(pwd,"./testdata2.h5"); -dataset2 = fullfile(pwd,'testdata2.h5'); - -% create hdf5 file and dataset > write data to hdf5 file / dataset -h5create(fpath, "/x1", size(x1), "Datatype", class(x1)) -h5create(fpath, "/y1", size(y1), "Datatype", class(y1)) -h5write(fpath, "/x1", x1) -h5write(fpath, "/y1", y1) - -%% Plotting -% This is still part of a normal script to produce plots. -% Make sure to save each figure in a variable to pass to PlotID-functions. -fig(1) = figure; -set(gcf,'Units','centimeters','PaperUnits','centimeters','PaperSize',[9 7],... -'Position',[5 5 9 7]); -plot(x,y,'Color',0.5*[1 1 1]); -box off; hold on; -plot(x1,y1,'-k'); -set(gca, 'TickDir', 'out', 'YLim', [0,4],'YTick',[0:1:4],'XLim',[0,6]); -%fstplt.setfiguresize('1/2ppt16:9'); -%fstplt.pimpplot; - -%% Example 1: single plot based on two data-sets - -%% Tag the plot -% PlotID Implementation starts here. -% TagPlot adds a visible ID to the figure(s) and to the figures property -% 'Tag' -[fig, ID] = PlotID.TagPlot(fig, 'ProjectID', ProjectID); - -%% Publishing -% Second part of plotID -% The functions needs the file location, the location of the data and the -% figure and can take several options. -path.script = mfilename('fullpath'); % filename of the m.script -% file names of the datasets -path.rdata = {dataset1,dataset2} ; % don't forget the extension - -PlotID.Publish(path, ID, fig(1), 'Location', 'local' ,'Method','individual') - - - -%% End - diff --git a/README.md b/README.md index 83e88a152b92188d60874df012febdad41e3bce5..342cf55fc3bc97c07fcf011bc7ef83f2b9744015 100644 --- a/README.md +++ b/README.md @@ -11,20 +11,23 @@ Feel free to give feedback and feature requests or to take part in the developme [TOC] # Quick User Guide +**Requirements:** MATLAB R2021a or newer + `PlotID` works in two Steps: 1. tagging the plot `[figs, IDs] = plotId.TagPlot(figs, options)` +You should either set a ProjectID in the config.json (copy & rename the 'example-config.json' to 'config.json'), or pass it as option 'ProjectID' to the TagPlot function. +2. publish the plot and the associated data +`plotID.Publish(DataPaths,scriptPath, figure, options)` -2. publishing the plot and the associated data -`plotID.Publish(DataPaths, ID, figure, options)` - -`DataPaths.script = mfilename('fullpath');` contains the filename of the m.script - -`DataPaths.rdata = {dataset1, dataset2};` file names of the datasets (they most be at the path) +`scriptPath` contains the path to the script, this can be provided with the simple call of `scriptPath = [mfilename('fullpath'),'.m']`, note that + **the extension is mandatory.** -You should either set a ProjectID in the config.json (copy & rename the 'example-config.json' to 'config.json'), or pass it as option 'ProjectID' to the TagPlot function. +`DataPaths` contains the path(s) to the research data, for multiple files you can use a cell arrays (they must be at the path). +`figure` is the figure that should be published. + # PlotID.TagPlot() `[figs, IDs] = TagPlot(figs, options)` @@ -63,7 +66,7 @@ FriendlyID Changes the Hex Number to a human friendly *datetime* and *dateStr*. # PlotID.Publish() -`Publish(DataPaths, ID, figure, options)` \ +`Publish(DataPaths,scriptPath, figure, options)` \ Publishes saves plot, data and measuring script Location sets the storage location. 'local' sets the storage location to the current folder (an export folder will be created), 'server' is a remote path, that is defined in the config file. Two Methods are implemented 'individual' stores the data for each plot while 'centralized' uses a data folder and uses reference links to the original data (hdf5 only). ParentFolder is the folder Name where the exported data is stored if a path is used, PlotId will use this path a storagePath diff --git a/example-config.json b/example-config.json deleted file mode 100644 index 92b8621a1d43e5fc26290fa6acce3be448471d3e..0000000000000000000000000000000000000000 --- a/example-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "Author": "Example Author", - "ProjectID": "AB01", - "ServerPath": "\\\\Server\\path\\folder" -} diff --git a/example.m b/example.m index 04aa8c11aebc102d0810c7e23681ad1ba473d943..b64044db476204bb46edce6332f08ddb80be7573 100644 --- a/example.m +++ b/example.m @@ -43,7 +43,8 @@ h5write(fpath, "/y1", y1) % Place for post-processing of the data, or additional related code. % example_fcn is a dummy function to show the functionality a = 1; a = example_fcn(a); -p = betacdf(0.5,1,1); % to test toolboxes +% Uncomment to include the Statistics and Machine learning Toolbox +% p = betacdf(0.5,1,1); % to test toolboxes %% Plotting % This is still part of a normal script to produce plots. @@ -61,21 +62,24 @@ set(gca, 'TickDir', 'out', 'YLim', [0,4]); % PlotID Implementation starts here. % TagPlot adds a visible ID to the figure(s) and to the figures property % 'Tag' -[fig, ID] = PlotID.TagPlot(fig, 'ProjectID', ProjectID); - +% [fig, ID] = PlotID.TagPlot(fig, 'ProjectID', ProjectID); +[fig, ID] = PlotID.TagPlot(fig); %% Publishing % Second part of plotID % The functions needs the file location of the script, the location of the % data and the figure and can take several options (see readme). -path.script = mfilename('fullpath'); % filename of the m.script +% filename of the m.script, extension must be added! +scriptPath = [mfilename('fullpath'),'.m']; + % file names of the datasets %(defined above:) dataset1 = 'test_data.mat'; dataset2 = 'testdata2.h5' -path.rdata = {dataset1,dataset2} ; % don't forget the extension +locations = {dataset1,dataset2} ; % don't forget the extension +locations = {string(dataset1),dataset2}; %call publishing -PlotID.Publish(path, ID, fig(1), 'Location', 'local' ,'Method','individual') +PlotID.Publish(locations,scriptPath, fig(1), 'Location', 'local' ,'Method','individual') %% Example 2: multiple plots plot, all based on dataset2 (hdf5) % for individual data-sets, use an appropriate array @@ -90,13 +94,13 @@ set(gca, 'TickDir', 'out', 'YLim', [0,4]); [fig, IDs] = PlotID.TagPlot(fig,'ProjectID', ProjectID); % data locations -path.script = mfilename('fullpath'); % filename of the m.script +script = [mfilename('fullpath'),'.m']; % filename of the m.script % file names of the datasets -path.rdata = {dataset2} ; % don't forget the extension +rdata = dataset2 ; % don't forget the extension % publsihing via a for-loop for i=1: numel(fig) - PlotID.Publish(path, IDs{i}, fig(i), 'Location', 'local',... + PlotID.Publish(rdata, script, fig(i), 'Location', 'local',... 'Method','individual'); end @@ -114,4 +118,4 @@ plot(x1,y1,'-r'); [fig2, ID] = PlotID.TagPlot(fig2,'ProjectID', ProjectID); -PlotID.Publish(path, ID, fig2, 'Location', 'local','Method','centralized') +PlotID.Publish(locations,scriptPath, fig2, 'Location', 'local','Method','centralized')