diff --git a/+PlotID/@config/config.m b/+PlotID/@config/config.m index efb0f4e3ca3a1e232375e2c04dad7cbfde6a2271..b9b4779ef6f87e15119fefcd9d1063745711ff48 100644 --- a/+PlotID/@config/config.m +++ b/+PlotID/@config/config.m @@ -1,7 +1,7 @@ classdef config < handle - %CONFIG class handles methods and attributes related to the config + % CONFIG class handles methods and attributes related to the config %file - %handle class used for dynamicy property updates + % handle class used for dynamicy property updates properties (SetAccess = private) mandatoryFields = {'Author', 'ProjectID'} @@ -21,15 +21,23 @@ classdef config < handle % reads config data from config file, if an error occurs the % wizard is started by the catch block obj.configFileName = configFileName; - try - txt = fileread(obj.configFileName); + try %Validity Check + tmp = what("PlotID"); + tmp = strrep(tmp.path,'+PlotID',''); + defaultConfigPath = fullfile(tmp,obj.configFileName); + if isfile(defaultConfigPath) + txt = fileread(defaultConfigPath); + else %search on path + txt = fileread(obj.configFileName); + end + obj.configData = jsondecode(txt); assert(checkConfig(obj)); if isfield(obj.configData,'ExportPath') obj.exportPath = obj.configData.ExportPath; obj.configData.options.Location = 'exportPath'; end - catch + catch msg = ['no valid config File with the filename ',... obj.configFileName, ' found.' newline,... 'Please enter the required information manually']; @@ -45,7 +53,7 @@ classdef config < handle function outputArg = checkConfig(obj) %checkConfig validates the config file - % 1. Check if mandatory Fields are set + % 1. check if mandatory fields are set check = isfield(obj.configData,obj.mandatoryFields); outputArg = all(check); diff --git a/+PlotID/@dataPath/dataPath.m b/+PlotID/@dataPath/dataPath.m new file mode 100644 index 0000000000000000000000000000000000000000..2edf934401bf60bcd6a00e15385c3d075a8e3278 --- /dev/null +++ b/+PlotID/@dataPath/dataPath.m @@ -0,0 +1,86 @@ +classdef dataPath < handle + % DATAPATH stores the datapaths to the research data + % usage of a class is neccessary to support variables, argument + % validation and storing variables as tempory files + + properties (SetAccess = protected) + DataPaths (1,:) cell % dataPaths + tmpPath % path to TMP files + ID %PlotID + dataFolderName = 'data' %name for the data folder when using centralized + end + + methods + function obj = dataPath(inputPaths,ID) + %DATAPATH Construct an instance of this class + % start with argument validation + obj.ID = ID; + + %catch non cell inputs in inputPaths + if ~iscell(inputPaths) + inputPaths = {inputPaths}; %Cell array + end + + + + % handle nested cell arrays + while any(cellfun(@iscell,inputPaths)) % while any of the cells contain cells + % concatenate the content of the cells containing cells + % with the ones that don't contain cells + inputPaths = [inputPaths{cellfun(@iscell,inputPaths)} inputPaths(~cellfun(@iscell,inputPaths))]; + end + + isStruct = false([1,numel(inputPaths)]); + % strings will cause problems, therefore chars are used + for i=1:numel(inputPaths) + if isstring(inputPaths{i}) + inputPaths{i} = char(inputPaths{i}); + end + % check for Variable inputs + if isstruct(inputPaths{i}) + isStruct(i) = true; + elseif ~ischar(inputPaths{i}) + obj.throwError(); + end + end + + obj.DataPaths = inputPaths; + % create temporary file from all Variables + if any(isStruct) + obj.vars2file(isStruct); + end + + % final check if all paths are valid + mustBeDataPath(obj); + end + + function mustBeDataPath(obj) + if ~isempty(obj.DataPaths) + %checks if input is a valid DataPath object + tf = ~isfile(obj.DataPaths); + if any(tf) + dataPath.throwError(); + end + end + end + + + function cleanTmpFile(obj) + if ~isempty(obj.tmpPath) + delete(obj.tmpPath{:}); + end + end + + %% non local functions + vars2file(obj,isvar); + end + methods (Static) + function throwError() + %THROWERROR throws an error Dialog for invalid Data + eidType = 'mustBeDataPath:notaValidFilePath'; + msgType = 'DataPaths must contain file-path(s) or Variables'; + throwAsCaller(MException(eidType,msgType)) + end + end %static +end %class + diff --git a/+PlotID/@dataPath/vars2file.m b/+PlotID/@dataPath/vars2file.m new file mode 100644 index 0000000000000000000000000000000000000000..f7c2ac32194995a5dd3d2ccead02c06e67f600ff --- /dev/null +++ b/+PlotID/@dataPath/vars2file.m @@ -0,0 +1,32 @@ +function obj = vars2file(obj,isStruct) +%vars2file creates a temporary file for all variables in inputPaths +% the number of inputPaths is reduced by the number of variables + +tmpEnv= getenv('TMP'); +tmpDir = fullfile(tmpEnv,'PlotID'); +if ~isfolder(tmpDir) + mkdir(tmpDir); +end +obj.tmpPath = fullfile(tmpDir,[obj.ID,'_vars.mat']); + +% save each of the structs +for i=1:numel({obj.DataPaths{isStruct}}) + struct = obj.DataPaths{isStruct}; + if i==1 + save(obj.tmpPath,'-struct','struct'); + obj.tmpPath = {obj.tmpPath}; + else + tmpPath = fullfile(tmpDir,[obj.ID,'_vars',num2str(i),'.mat']); + save(tmpPath,'-struct','struct'); + obj.tmpPath{end+1}=tmpPath; + end +end + +% remove variable from Datapath +obj.DataPaths(isStruct) = []; +% add tmppath as Datapath + +obj.DataPaths = horzcat(obj.DataPaths, obj.tmpPath); + +end + diff --git a/+PlotID/@userDLG/userDLG.m b/+PlotID/@userDLG/userDLG.m index 7b82f79cd84dfb8102cdd84412971710b8ad6765..579d1117633d59988ea305a4a349e7e84d810b56 100644 --- a/+PlotID/@userDLG/userDLG.m +++ b/+PlotID/@userDLG/userDLG.m @@ -1,19 +1,19 @@ classdef userDLG - %userDLG the userDLG Class is responsible for the user dialog + % 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 = '' + configError = false % error state of reading the config + scriptPublished = false % error state of publishing the script + rdFilesPublished = false % error state of publishing the research data + figurePublished = false % error state of publishing the figure + msg = '' % message to the user end properties (SetAccess = protected) - success = false + success = false % product of all states showMessages {mustBeNumericOrLogical} forcePublish {mustBeNumericOrLogical} ID %plotID diff --git a/+PlotID/CreateID.m b/+PlotID/CreateID.m index 2579717c343c33a6a777e3458e85fb29f168325d..9ae087d8c98afe515c38e236608b8abaa39b3cab 100644 --- a/+PlotID/CreateID.m +++ b/+PlotID/CreateID.m @@ -1,5 +1,5 @@ function [ID] = CreateID(method) -%CreateID Creates an identifier (char) +% CreateID Creates an identifier (char) % Creates an (sometimes unique) identifier based on the selected method % if no method is selected method 1 will be the default method arguments diff --git a/+PlotID/Publish.m b/+PlotID/Publish.m index 9c845ed637e7b3ed2b214c8851bcd1822919896f..909dc03cfa54528ace7aed66a63d4afe0126c61c 100644 --- a/+PlotID/Publish.m +++ b/+PlotID/Publish.m @@ -1,21 +1,22 @@ function Publish(DataPaths,scriptPath, figure, options) -%%Publish(DataPaths,scriptPath, ID, figure, options) saves plot, data and measuring script +% 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 +% use a cell array or ONE struct of variables (only the first struct will +% be exported) % scriptPath contains the path to the script, this can be provided with % the simple call of scriptPath = [mfilename('fullpath'),'.m'] % % Options: % Location sets the storage location. 'local' sets the storage location % to the current folder (an export folder will be created), 'manual' opens -% a explorer window, where you can choose the folder. If you define a export -% path in the config file, this will used per defaul (exportPath). +% an explorer window, where you can choose the folder. If you define a export +% path in the config file, this will used per default (exportPath). % 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 an +% 'ParentFolder' is the folder name where the exported data is stored if an % path is used, PlotId will use this path as storagePath % 'ConfigFileName' is needed for handling multiple config files (see example) % 'CSV' turns a summary table of all exports on or off @@ -26,7 +27,7 @@ function Publish(DataPaths,scriptPath, figure, options) % 'ForcePublish' will publish even if one step was not successful (not recommended) arguments - DataPaths {mustBeDataPath} % location of the data-file(s) + DataPaths % location of the data-file(s) and/or ONE struct of variables scriptPath (1,:) {mustBeText} % location of the matlab script figure (1,:) {mustBeFigure} % Checks if figure is a figure object options.Location {mustBeMember(options.Location ,{'local','exportPath','manual','CI-Test'})} = 'local' % storage path @@ -37,22 +38,10 @@ arguments options.CSV (1,1) {mustBeNumericOrLogical} = true options.ShowMessages(1,1) {mustBeNumericOrLogical} = false 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 - % strings will cause problems, therefore chars are used -for i=1:numel(DataPaths) - if isstring(DataPaths{i}) - DataPaths{i} = char(DataPaths{i}); - end -end - if isstring(scriptPath) scriptPath = char(scriptPath); end @@ -95,6 +84,9 @@ if isempty(ID) warning(msg); end +%% Create a Datapath object from Input +dataObj = PlotID.dataPath(DataPaths,ID); + %% read config file % there is only one config Object (handleClass) configObj = PlotID.config(options.ConfigFileName); @@ -124,7 +116,7 @@ switch options.Location if isfolder(configObj.exportPath) storPath = configObj.exportPath; else - msg = ['Your Export folder ', storPath, newline,... + msg = ['Your Export folder ', configObj.exportPath, newline,... 'does not exist, check the config file - publishing not possible!']; dlgObj.error(msg); end @@ -169,66 +161,17 @@ dlgObj.userMSG(msg); % user functions if options.CopyUserFCN - [fList,pList] = matlab.codetools.requiredFilesAndProducts(scriptPath); - 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 PlotID - if ~isempty(fList) - PlotID.createFileCopy(fList,folderName,storPath,ID,'userFcn'); - end + toolboxList = PlotID.copyUserFCN(scriptPath, folderName, storPath, ID); end %% Research data handling switch options.Method case 'centralized' - DataFolderName = 'data'; - % check if data folder exists - if ~isfolder(fullfile(storPath,DataFolderName)) - mkdir(fullfile(storPath,DataFolderName)); - end - % to get relative Paths - currentPath = fullfile(storPath); + [status, msg] = PlotID.centralized(storPath, dataObj, folderName); - %list all files - 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) - % check if identical file exists (status = 1) - [~, idx] = PlotID.fileCompare(DataPaths{i},fList); - % create Linked HDF5 files for identical files - if any(idx) - fList.path = fullfile(fList.folder,fList.name); - sourcePath = fList{idx,'path'}; - if ~iscell(sourcePath) - sourcePath = {sourcePath}; - end - relativeSourcePath = strrep(sourcePath,currentPath,''); - - if contains(sourcePath,{'.h5','.hdf5'}) % Linking only for HDF5 - 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, status, msg] = PlotID.createFileCopy(DataPaths{i},'data',storPath,ID,'dataCentral'); - pathToData = strrep(dataPath,currentPath,''); - %WIP - if contains(DataPaths{i},{'.h5','.hdf5'}) % Linking only for HDF5 - % and create also linked files in the plot folder - 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 - [~, status, msg] = PlotID.createFileCopy(DataPaths,folderName,storPath,ID, 'data'); + [~, status, msg] = PlotID.createFileCopy(dataObj.DataPaths,folderName,storPath,ID, 'data'); end %temporary: dlgObj.rdFilesPublished = status; @@ -247,7 +190,7 @@ meta.ProjectID = ID; meta.CreationDate = datestr(now); meta.MatlabVersion = version; if options.CopyUserFCN - meta.ToolboxVersions = pList; + meta.ToolboxVersions = toolboxList; end % write meta metaPath = fullfile(storPath,folderName,'plotID_data.json'); @@ -282,11 +225,13 @@ if dlgObj.success || options.ForcePublish end status = movefile(oldPath,newPath); %rename directory end +% delete tmp data +dataObj.cleanTmpFile(); %% CSV export if options.CSV T = table(); - T.research_Data = DataPaths'; T.PlotID(:) = {ID}; + T.research_Data = dataObj.DataPaths'; T.PlotID(:) = {ID}; T.Author(:) = {configObj.configData.Author}; T.Script_Name(:) = {scriptPath}; T.Storage_Location(:) = {newPath}; @@ -310,21 +255,5 @@ function tf = mustBeFigure(h) 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 d940a5dc29e9cc7b4869e9f86eab8721b37437ed..d36e6910913d05e136427d9902fea84fb4fa0967 100644 --- a/+PlotID/TagPlot.m +++ b/+PlotID/TagPlot.m @@ -1,5 +1,5 @@ function [figs, IDs] = TagPlot(figs, options) -%TagPlot adds IDs to figures +% TagPlot adds IDs to figures % The ID is placed visual on the figure window and as Tag (property of figure) % TagPlot can tag multiple figures at once. % If a single Plot is tagged IDs is a char, otherwise it is a cell array of @@ -24,6 +24,9 @@ arguments options.Position (1,2) {mustBeVector} = [1,0.4] % default for east options.Rotation (1,1) {mustBeReal} = NaN options.ConfigFileName (1,:) {mustBeText} = 'config.json' + options.PinToLegend (1,1) {mustBeNumericOrLogical} = false % Pins ID on Legend + options.QRcode (1,1) {mustBeNumericOrLogical} = false %experimental + options.QRsize (1,1) {mustBeNonnegative} = 0.15 % size of the QRCode end if isempty(options.ProjectID) @@ -34,7 +37,7 @@ end switch options.Location case 'north' - y = 1 - options.DistanceToAxis + y = 1 - options.DistanceToAxis; options.Position = [0.4,y]; Rotation = 0; case 'east' @@ -73,16 +76,46 @@ IDs = cell(numel(figs),1); for n = 1:numel(figs) IDs{n} = PlotID.CreateID; % Create ID IDs{n} = [options.ProjectID,'-',IDs{n}]; % add options.ProjectID - axes = get(figs(n),'CurrentAxes'); % Axes object for text annotation - % Limits for relative Positioning - ylim =get(axes,'YLim'); - xlim =get(axes,'XLim'); - %ID - position = [options.Position(1), options.Position(2)]; - text(axes,position(1),position(2), IDs{n},'Fontsize',options.Fontsize,... + pltAxes = get(figs(n),'CurrentAxes'); % Axes object for text annotation + % Limits for relative positioning + ylim =get(pltAxes,'YLim'); + xlim =get(pltAxes,'XLim'); + + if options.PinToLegend + if options.QRcode + warning(['PinToLegend and QRCode can not be used at the same time',... + newline, 'QRcode is deactivated.']); + options.QRcode = false; + end + lobj = findobj(figs(n), 'Type', 'Legend'); + if isempty(lobj) + set(0, 'CurrentFigure', figs(n)) + lobj = legend(); + end + % new position based on legend + posVec = get(lobj,'Position'); + options.Position(1) = posVec(1)+.04; + options.Position(2) = posVec(2); + end + + % add text label + position = [options.Position(1), options.Position(2)]; + t=text(pltAxes,position(1),position(2), IDs{n},'Fontsize',options.Fontsize,... 'Rotation',Rotation, 'VerticalAlignment','bottom','Color',... options.Color,'BackgroundColor','w', 'Units', 'normalized'); - set(figs(n),'Tag', IDs{n}); + set(figs(n),'Tag', IDs{n}); + + + if options.QRcode + % this should be seen and use as a proof of concept + qrCode = PlotID.plotQR(IDs{n}); + size = options.QRsize; + axes('Position',[position(1)-.05 position(2)+0.1 size size]); + imshow(qrCode); + t.Visible = 'off'; + end + + end if numel(figs) == 1 diff --git a/+PlotID/centralized.m b/+PlotID/centralized.m new file mode 100644 index 0000000000000000000000000000000000000000..ff9b45bd94e3f49008ee939f8dd7a8eda97974a2 --- /dev/null +++ b/+PlotID/centralized.m @@ -0,0 +1,72 @@ +function [status, msg] = centralized(storPath, dataObj, folderName) +% CENTRALIZED stores the research files in a centrelized folder. +% Linked files are created for subsequent folders (HDF5 only). +% +% storPath for the Data +% dataObj contains tha data related information +% folderName is the Name of the storage Folder +msg = ''; +warning(['Linked HDF5 can only be moved with their ',... + 'respective master files in the ', dataObj.dataFolderName, ' folder.']); + +% check if data folder exists +if ~isfolder(fullfile(storPath,dataObj.dataFolderName)) + mkdir(fullfile(storPath,dataObj.dataFolderName)); +end +% to get relative Paths +currentPath = fullfile(storPath); + +%list all files +fList = dir(fullfile(storPath,dataObj.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(dataObj.DataPaths) + % check if identical file exists (status = 1) + [~, idx] = PlotID.fileCompare(dataObj.DataPaths{i},fList); + % create l inked HDF5 files for identical files + if any(idx) + fList.path = fullfile(fList.folder,fList.name); + sourcePath = fList{idx,'path'}; + if ~iscell(sourcePath) + sourcePath = {sourcePath}; + end + relativeSourcePath = strrep(sourcePath,currentPath,''); + + if contains(sourcePath,{'.h5','.hdf5'}) % Linking only for HDF5 + linkedHDFPath = fullfile(storPath,folderName); + [status] = PlotID.createLinkedHDF5(relativeSourcePath{1,1},linkedHDFPath); + end + else % no identical file exists + %Copy the file in data and create the links (if hdf5) + [dataPath, status, msg] = PlotID.createFileCopy(dataObj.DataPaths{i},... + 'data',storPath,dataObj.ID,'dataCentral'); + pathToData = strrep(dataPath,currentPath,''); + + if ~status + return; % an error orccured + end + + %WIP + if contains(dataObj.DataPaths{i},{'.h5','.hdf5'}) % Linking only for HDF5 + % and create also linked files in the plot folder + linkedHDFPath = fullfile(storPath,folderName); + [status] = PlotID.createLinkedHDF5(pathToData,linkedHDFPath); + else + warning(['You use the centralized method for non hdf files,',... + newline, 'Your research files are located in the ',... + DataFolederName , 'folder.']); + status = true; + end %if + end %if + % add do not move message + doNotMove = ['do not move this folder without the ',... + dataObj.dataFolderName, ' folder']; + fid = fopen(fullfile(storPath,folderName,[doNotMove,'.txt']),'w'); + fprintf(fid,doNotMove); fclose(fid); +end %for + +end \ No newline at end of file diff --git a/+PlotID/copyUserFCN.m b/+PlotID/copyUserFCN.m new file mode 100644 index 0000000000000000000000000000000000000000..cabf4904581b5793c7a128804cdc65c5c594ccfd --- /dev/null +++ b/+PlotID/copyUserFCN.m @@ -0,0 +1,64 @@ +function pList = copyUserFCN(scriptPath, folderName, storPath, ID) +% COPYUSERFCN copies all user functions, classes and toolboxes that are used +% by a script (scriptpath). +% +% All toolboxes and classes are copied as a whole. +% folderName, storPath and ID are required for createfilecopy +% see also createFileCopy + + [fList,pList] = matlab.codetools.requiredFilesAndProducts(scriptPath); + fList = fList(~ismember(fList,scriptPath)); % rmv plot script itself from list + fList = fList(~contains(fList,'config.json')); % rmv config.json from list + fList = removePltIdFiles(fList); % Do not copy files that are part of PlotID + + % copy Classes and Toolboxes as a whole + copyTBorClass(fList,'+', folderName, storPath, ID); % Toolboxes + copyTBorClass(fList,'@', folderName, storPath, ID); % Classes + %remove class and toolbox files from flist + fList = fList(~contains(fList,{'@','+'})); + + % copy User FCN + if ~isempty(fList) + PlotID.createFileCopy(fList,folderName,storPath,ID,'userFcn'); + end +end + +function [fListClean] = removePltIdFiles(fList) + %removePltIdFiles removes functions that are part of PlotID out of flist + % Detailed explanation goes here + + [~,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 + 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)); +end + +function [status, msg] = copyTBorClass(fList,leadChar, folderName, storPath, ID) +%copyTBorClass copies the Toolboxes or Classes that are part of flist +% lead char must be either '@' for Classes or '+' for Toolboxes +if any(contains(fList,leadChar)) + list = fList(contains(fList, leadChar)); + names = extractBetween(list,leadChar,filesep); + paths = extractBefore(list,strcat(leadChar,names)); + fullPaths = strcat(paths,strcat(leadChar,names)); + fullPaths = unique(fullPaths); + [~, status, msg] =PlotID.createFileCopy(fullPaths,... + folderName,storPath,ID,'class'); +end %if +end \ No newline at end of file diff --git a/+PlotID/createFileCopy.m b/+PlotID/createFileCopy.m index 9787b19dcd37e842f18440b1ad1cc3ff7e5f4b0d..297203c57e65a916d5e4b359b93616edbbf3b5ec 100644 --- a/+PlotID/createFileCopy.m +++ b/+PlotID/createFileCopy.m @@ -1,14 +1,17 @@ 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 +% createFileCopy 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 +% type switches the mode depending on the data type if ~iscell(filePaths) %fixes Issue if filepath is a char and not a cell array filePaths = {filePaths}; end -try +%try storagePaths = cell(numel(filePaths,1)); for i = 1:numel(filePaths) FileNameAndLocation = filePaths{i}; @@ -29,6 +32,8 @@ try case 'userFcn' %keep original name newfile = sprintf([name,ext]); + case {'class','toolbox'} + newfile = name; % copy whole folder otherwise error([type,' is not a valid type for createFileCopy']) end %switch @@ -36,46 +41,48 @@ try RemotePath = fullfile(storPath,folderName, newfile); % Check if remote file already exists - count = 0; - while isfile(RemotePath) && ismember(type,{'data','dataCentral'}) - % Add a Sufix number to new file name - % TODO add more inteligent way then a simple sufix - count = count + 1; - [~,name,ext] = fileparts(RemotePath); - if count < 2 - RemotePath = fullfile(storPath,folderName,... - [name,'_',num2str(count),ext]); - else - RemotePath = fullfile(storPath,folderName,... - [name(1:end-length(num2str(count))),num2str(count),ext]); - end - [~, name, ~] = fileparts(RemotePath); - + + if isfile(RemotePath) && ismember(type,{'data','dataCentral'}) + % Add a Sufix number to new file name + [~,name,ext] = fileparts(RemotePath); - msg = ['Filename ',name,... - ' already exists in the data folder' newline,... - ' PlotID will add an suffix if you continue.' newline,... - ' This can cause serious confusions.']; + % User Dialog + msg = ['Filename ',name, ' already exists in the data folder' newline,... + ' PlotID will add an suffix if you continue.' newline,... + ' This can cause serious confusions.']; warning(msg); m = input('Do you want to continue, Y/N [Y]:','s'); if ismember(m,{'N','n'}) errorMSG = ['Filename already exists in data folder.' newline,... ' Rename the File and restart PlotID.']; - error(); - end - end + error(errorMSG); + end + % add sufix + RemotePath = fullfile(storPath,folderName,... + [name,'_1',ext]); + + % auto count until a sufix is reached that did not + % exist. (User is only asked once) + count = 0; + while isfile(RemotePath) + count = count + 1; + [~,name,~] = fileparts(RemotePath); + RemotePath = fullfile(storPath,folderName,... + [name(1:end-length(num2str(count-1))),num2str(count),ext]); + end + end%if copyfile(FileNameAndLocation,RemotePath); storagePaths{i} = RemotePath; - end + end %for status = true; msg =([type, ' successfully published']); -catch - status = false; - warning([type,' export was not successful']) - if exist('errorMSG') - error(errorMSG); - end -end %try +% catch +% status = false; +% warning([type,' export was not successful']) +% if exist('errorMSG') +% error(errorMSG); +% end +% end %try end %function diff --git a/+PlotID/createLinkedHDF5.m b/+PlotID/createLinkedHDF5.m index f04a3b9f01120be4a22de929f30eb0aef6f09501..936c9c9c7a8ff5cb5b2dbea97cc2a0883c076d17 100644 --- a/+PlotID/createLinkedHDF5.m +++ b/+PlotID/createLinkedHDF5.m @@ -1,6 +1,6 @@ function [status] = createLinkedHDF5(SourceFile,TargetPath) -%createLinkedHDF5 creates a HDF file that references the Sourcefile -% TargetPath is the storage location, ID the foldername +% createLinkedHDF5 creates a HDF file that references the Sourcefile +% TargetPath is the storage location % Status returns true if the function was successfull plist_id = 'H5P_DEFAULT'; diff --git a/+PlotID/fileCompare.m b/+PlotID/fileCompare.m index 9d0f9694ddf0bef7b291716728f3686024ba3e69..43ca371ce0d0ac08c276aa1ed073f197580979d2 100644 --- a/+PlotID/fileCompare.m +++ b/+PlotID/fileCompare.m @@ -1,9 +1,10 @@ function [status, id] = fileCompare(filename,fileList) -%% fileCompare checks if file1 is (binary) identical to a file in filelist +% fileCompare checks if file1 is (binary) identical to a file in filelist % it returns a status and the id of the identical file -% the functions uses the java libraries from matlab for windows systems -% or the unix function diff -% because of performance issues with windows system calls in matlab +% +% the functions uses the java libraries from matlab for windows systems +% or the unix function diff because of performance issues with windows +% system calls in matlab if isempty(fileList) % no comparison necessary diff --git a/+PlotID/friendlyID.m b/+PlotID/friendlyID.m index cdac106b417794cf287ea9cab706741a4bb25b9c..1a26af21948b68f3ab206b1273f49eeaf3d2ae87 100644 --- a/+PlotID/friendlyID.m +++ b/+PlotID/friendlyID.m @@ -1,7 +1,8 @@ function [IDf,PrjID, Str] = friendlyID(ID) -%FriendlyID Changes the Hex Number to a human friendly datetime and dateStr -% IDf ID as DateTime Object, PrjID returns the ProjectID, Str returns the -% timestamp as String +% FriendlyID Changes the Hex Number to a human friendly datetime and dateStr +% +% IDf ID as DateTime Object, PrjID returns the ProjectID, Str returns the +% timestamp as String %Remove Prefix if contains(ID,'-') diff --git a/+PlotID/plotQR.m b/+PlotID/plotQR.m new file mode 100644 index 0000000000000000000000000000000000000000..6cdccc74d98726e6937259c0f2cdbf6478526249 --- /dev/null +++ b/+PlotID/plotQR.m @@ -0,0 +1,25 @@ +function img = plotQR(qrTxt, size) +% plotQR creates a QR code based on qrTxt by calling a qrserver (depends on +% API) +% size specifies the absolut size of the qr code (pixels) +% special thanks to J.Stifter for this suggestion + + arguments + qrTxt {mustBeTextScalar} = 'example'; + size (1,2) {mustBeInteger} = [150, 150]; + end + + m = size(1); + n = size(2); + + base_api = 'https://api.qrserver.com/v1/create-qr-code/?size=%dx%d&data=%s'; + request = sprintf(base_api, m, n, qrTxt); + + options = weboptions; + options.Timeout = 5; + + img = webread(request, options); + img =logical(img); + +end + diff --git a/+PlotID/removePltIdFiles.m b/+PlotID/removePltIdFiles.m deleted file mode 100644 index 57a07b7c9a7ea20f7d4a209402729bc469aac232..0000000000000000000000000000000000000000 --- a/+PlotID/removePltIdFiles.m +++ /dev/null @@ -1,27 +0,0 @@ -function [fListClean] = removePltIdFiles(fList) -%removePltIdFiles removes functions that are part of PlotID out of flist -% Detailed explanation goes here - -[~,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 -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)); -end - - diff --git a/+PlotID/replaceLinkedHDF5.m b/+PlotID/replaceLinkedHDF5.m index e84e49f6e3aeed36d24f59b50288984fbe4d3998..07f3435998e6e414603a37f678c40931881fe605 100644 --- a/+PlotID/replaceLinkedHDF5.m +++ b/+PlotID/replaceLinkedHDF5.m @@ -6,8 +6,6 @@ function replaceLinkedHDF5(linkedFilepath) % Filepath is the location of the File that contains the link. The % filepath to the linked data has to be present in the file itself. - - %% Check whether the only Objects present are links h5inf = h5info(linkedFilepath); diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b4d78bb0e40b64d2c595a8ebd3689aaf384b27..4dab2251b56ff03ea7d2b0badf06fa7cace0993c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -# V0.3 +# V1.0 RC1 - Add changelog - Add support for file or function name as scriptPath -- Fix a Bug in HDF linking +- Add support for passing Variables in publish +- Move centralized method in fuction +- Improve usability +- Improve documentation diff --git a/Examples/PlotID_QRcode.m b/Examples/PlotID_QRcode.m new file mode 100644 index 0000000000000000000000000000000000000000..c3308984ea3584fca2444117a097f3b900a116ef --- /dev/null +++ b/Examples/PlotID_QRcode.m @@ -0,0 +1,14 @@ +clear; close all; clc; +%% Tag the plot with a QR code (experimental) +% here the work flow for tagging the plot with a QR code is shown +% the content of the qr code is the ID + +% plots and data +fig(1) = figure; +[x1, y1, datapath1] = createExampleData('matlab'); +plot(x1,y1,'-b'); box off; hold on; set(gca, 'TickDir', 'out', 'YLim', [0,4]); + + +%% 1. Tag plot with QRcode +% QR size is the relative size of the QR code (0.15 default) +[fig, IDs] = PlotID.TagPlot(fig,'Location','southeast','QRcode',true, 'QRsize', 0.18); diff --git a/Examples/PlodID_advanced.m b/Examples/PlotID_advanced.m similarity index 100% rename from Examples/PlodID_advanced.m rename to Examples/PlotID_advanced.m diff --git a/Examples/PlotID_centralized.m b/Examples/PlotID_centralized.m index 33209f2d2a3864d40672f763136ac23666e84eda..921917e2ed0cafbb636fd940eccefda3da506b2f 100644 --- a/Examples/PlotID_centralized.m +++ b/Examples/PlotID_centralized.m @@ -22,4 +22,12 @@ fig2 = figure; plot(x.^2,y,'-r'); [fig2, ID] = PlotID.TagPlot(fig2); -PlotID.Publish(datapath,scriptPath, fig2, 'Method','centralized') \ No newline at end of file +PlotID.Publish(datapath,scriptPath, fig2, 'Method','centralized') + +%% Note: +% If you rerun this script PlotId will tell you that a identical named (but +% not binary idetical) file exists in the data folder. This is intended, +% due protect the user from overwritting non (binary) identical files. The +% reason for this is that two hdf files with the same data are not idetical +% when recreated (see line 11). Usually you would use an existing file +% without changing it. \ No newline at end of file diff --git a/Examples/PlotID_variables.m b/Examples/PlotID_variables.m new file mode 100644 index 0000000000000000000000000000000000000000..1d34b745d36e96c417bac29ab1550be936cda4d4 --- /dev/null +++ b/Examples/PlotID_variables.m @@ -0,0 +1,33 @@ +clear; clc; close all; +%% Example Script - How to pass Variables +% This script how to pass variables instead or additional to the DataPaths + +%% Data (only necessary for this example) +[x, y, datapath] = createExampleData('matlab'); +scriptPath = mfilename('fullpath'); + +%% Plotting +% This is still part of a normal script to produce plots. +% Make sure to save each figure in a variable to pass it to PlotID-functions. +fig1 = figure; +plot(x,y,'-k'); box off; set(gca, 'TickDir', 'out', 'YLim', [0,4]); +[fig1, ID] = PlotID.TagPlot(fig1); + +%% ---- 2. Publishing ----- +% You can pass an abitrary number of variables to Publish. +% Passing multiple structs will create multiple data files. +% You can additionally add datapaths and combine the methods. + + +%Example: Passing the variables x,y to publish + +s.x =x; s.y=y; % Save both variables in struct s + +% Build the locations cell with the struct and one path or an array of paths +locations = {s,datapath}; + +%call publish +PlotID.Publish(locations,scriptPath, fig1) + +% Your plot, script, the variables x,y and the path that your provided +% are now published. diff --git a/PlotID_example.m b/PlotID_example.m index 92099efa2997dd5dba4fd14bde1bb89f31314199..33deaf19ac8f385537f2d543099f6906d777eca4 100644 --- a/PlotID_example.m +++ b/PlotID_example.m @@ -53,6 +53,7 @@ fig2 = figure; plot(x,y,'-k'); box off; set(gca, 'TickDir', 'out', 'YLim', [0,4] % Publish(DataPaths,scriptPath, figure, options) % Path of the m.script that you use for creating your plot. +% note: mfilename only works if called from within a script or function scriptPath = [mfilename('fullpath'),'.m']; % file paths of the datasets used for the plot (don't forget the extension) @@ -63,11 +64,12 @@ locations = datapath; %call publish PlotID.Publish(locations,scriptPath, fig2) -% Your plot, script and all the data that your provide are now published. +% Your plot, script and all the data that you provided are now published. % --------------------------------- %% Further examples and help % You find more examples in the Examples folder: +% - Passing Variables % - Advanced usage % - Working with HDF5-files % - Using a central data folder diff --git a/README.md b/README.md index 4999db61898459829c6241e3f906936d3d0a8fec..5379313da8661fdf730975026852af94fe0ce2e4 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,10 @@ The **ProjectID** is your custom project number, it well be the prefix of the ID `PlotID.Publish(DataPaths,scriptPath, figure, options)` `scriptPath` contains either the path to the script, this can be provided with the simple call of `scriptPath = [mfilename('fullpath'),'.m']` or the script or function name that is used for creating your plot. \ -`DataPaths` contains the path(s) to the research data, for multiple files you can use a cell array (they must be at the path). \ +`DataPaths` contains the path(s) to the research data, for multiple files you can use a cell array (We recommend using absolute paths). It is also possible to pass an arbitrary number of variables as struct. \ `figure` is the figure that should be published. + +**Please take also a look on the examples provided in the Example folder**. # PlotID.TagPlot() @@ -46,12 +48,20 @@ you find the options for TagPlot below. The data type is in curled braces and th - ProjectID {mustBeText}= '' - Fontsize {mustBeInteger} = 8 +- Color {mustBeNonnegative} = 0.65*[1 1 1] - Location {mustBeText} = 'east' - Position {mustBeVector} = [1,0.4] -- Rotation {mustBeInteger} = 0 +- DistanceToAxis {mustBeReal} = .02 +- Rotation {mustBeInteger} = 0 +- PinToLegend {mustBeNumericOrLogical} = false +- QRcode {mustBeNumericOrLogical} = false +- QRsize {mustBeNonnegative} = 0.15 `options.ProjectID` is the project number (string or char), if empty the ID from the config file is used The ID is placed on the 'east' of the figure per default, if you want it somwhere else, use the `'Location'` option. 'north', 'east', 'south', 'west' are predefined, otherwise use the `'custom'` option and provide the desired 'Position' as a vector relative to your x- and y-axis limits `[relX, relY]` . + +`options.PinToLegend` pins the ID to the figure's legend. If no legend was defined, plotID will create one. + </details> ## CreateID() @@ -72,7 +82,9 @@ FriendlyID Changes the Hex Number to a human friendly *datetime* and *dateStr*. # PlotID.Publish() `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 +Location sets the storage location. 'local' sets the storage location to the current folder (an export folder will be created), 'manual' opens an explorer window, where you can choose the desired folder. If you define a export path in the config file, this will used per default (exportPath). + +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. <details><summary>detailed description</summary> @@ -156,5 +168,6 @@ you need to provide the name of your custom config file to tagplot as well as to The authors would like to thank the Federal Government and the Heads of Government of the Länder, as well as the Joint Science Conference (GWK), for their funding and support within the framework of the NFDI4Ing consortium. Funded by the German Research Foundation (DFG) - project number 442146713. # Known Issues +**Closed figure will cause error** Please do not cloase the figure, MATLAB deletes the object if you do so. # FAQs