diff --git a/src/MATLAB/+Dev/BuildDependencyGraph.m b/src/MATLAB/+Dev/BuildDependencyGraph.m
new file mode 100644
index 0000000000000000000000000000000000000000..517cc444a11fedbf72eb5dafee55511f88c9e7a0
--- /dev/null
+++ b/src/MATLAB/+Dev/BuildDependencyGraph.m
@@ -0,0 +1,108 @@
+function graphStruct = BuildDependencyGraph(chkPath, bRecurseExternal)
+    if ( ~exist('chkPath','var') || isempty(chkPath) )
+        chkPath = pwd();
+    end
+    
+    if ( ~exist('bRecurseExternal','var'))
+        bRecurseExternal = true;
+    end
+    
+    %% Make sure we get back to our current dir even on error.
+    oldDir = cd(chkPath);
+    cleanupObj = onCleanup(@()(cd(oldDir)));
+    
+    %% 
+    filenames = getAllFiles(chkPath);
+    
+    %% Initialize sparse matrix assuming a fanout of about 10x
+    n = length(filenames);
+    graphStruct = struct('nodes',{filenames}, 'graph',{sparse([],[],[],n,n,10*n)});
+    
+    graphStruct = recursiveGetDeps(chkPath, graphStruct, filenames, bRecurseExternal);
+    graphStruct = sortNodes(chkPath,graphStruct);
+end
+
+function graphStruct = sortNodes(localPath, graphStruct)
+    bLocal = strncmp(localPath,graphStruct.nodes, length(localPath));
+    localIdx = find(bLocal);
+    externalIdx = find(~bLocal);
+    
+    %% Sort lexicographically, but all local functions are first.
+    [~,localSrt] = sort(graphStruct.nodes(bLocal));
+    [~,externalSrt] = sort(graphStruct.nodes(~bLocal));
+    
+    srtIdx = [localIdx(localSrt); externalIdx(externalSrt)];
+    
+    graphStruct.nodes = graphStruct.nodes(srtIdx);
+    graphStruct.graph = graphStruct.graph(srtIdx,:);
+    graphStruct.graph = graphStruct.graph(:,srtIdx);
+end
+
+function graphStruct = recursiveGetDeps(localPath,graphStruct, checkNames, bRecurseExternal)
+    if ( isempty(checkNames) )
+        return;
+    end
+
+    newEntries = {};
+    
+    matRoot = matlabroot();
+    % Get single-link dependencies
+    for i=1:length(checkNames)
+        [fList,pList] = matlab.codetools.requiredFilesAndProducts(checkNames{i}, 'toponly');
+        toolboxes = arrayfun(@(x)(fullfile(matRoot,'toolbox',x.Name)),pList, 'UniformOutput',false);
+
+        selfIdx = find(strcmp(checkNames{i},fList));
+        if ( isempty(selfIdx) )
+            selfIdx = 1;
+            fList = [checkNames(i) fList];
+        end
+        
+        newNodes = [fList.'; toolboxes.'];
+        newGraph = createCallGraph(selfIdx, newNodes);
+        newStruct = struct('nodes',{newNodes},'graph',{newGraph});
+
+        [graphStruct,addedNodes] = Dev.MergeGraphStruct(graphStruct, newStruct);
+        newEntries = [newEntries; addedNodes];
+    end
+    
+    % Don't recurse through external dependencies
+    bMatlab = strncmp(matRoot,newEntries, length(matRoot));
+    newEntries = newEntries(~bMatlab);
+    if ( ~bRecurseExternal )
+        bNewLocal = strncmp(localPath,newEntries, length(localPath));
+        newEntries = newEntries(bNewLocal);
+    end
+    
+    graphStruct = recursiveGetDeps(localPath,graphStruct, newEntries, bRecurseExternal);
+end
+
+function callGraph = createCallGraph(callerIdx,newNodes)
+    jIdx = setdiff(1:length(newNodes),callerIdx);
+    iIdx = repmat(callerIdx,1,length(jIdx));
+
+    callGraph = sparse(iIdx,jIdx, ones(1,length(jIdx)), length(newNodes),length(newNodes), length(jIdx));
+end
+
+function fullNames = getAllFiles(dirName)
+    matlabFiles = what(dirName);
+
+    funcFileNames = vertcat(matlabFiles.m);
+    funcFileNames = [funcFileNames; vertcat(matlabFiles.mex)];
+    
+    fullNames = cellfun(@(x)(fullfile(dirName,x)), funcFileNames, 'UniformOutput',false);
+
+    for i=1:length(matlabFiles.packages)
+        pkgFullNames = getAllFiles(fullfile(dirName, ['+' matlabFiles.packages{i}]));
+        fullNames = [fullNames; pkgFullNames];
+    end
+    
+    for i=1:length(matlabFiles.classes)
+        classDir = fullfile(dirName, ['@' matlabFiles.classes{i}]);
+        if ( ~exist(classDir,'dir') )
+            continue;
+        end
+        
+        classFullNames = getAllFiles(classDir);
+        fullNames = [fullNames; classFullNames];
+    end
+end
diff --git a/src/MATLAB/+Dev/BuildToolboxMap.m b/src/MATLAB/+Dev/BuildToolboxMap.m
new file mode 100644
index 0000000000000000000000000000000000000000..b1f67fcbe607c9174cad82fe06653a3148cdfc9d
--- /dev/null
+++ b/src/MATLAB/+Dev/BuildToolboxMap.m
@@ -0,0 +1,27 @@
+function toolboxMap = BuildToolboxMap()
+    toolboxRoot = toolboxdir('');
+    toolboxMap = containers.Map('keyType','char', 'valueType','any');
+    
+    % Ignore fixPoint, because
+    toolboxList = dir(toolboxRoot);
+    
+    bInvalidName = arrayfun(@(x)(strcmp(x.name,'.') || strcmp(x.name,'..') || strcmp(x.name,'fixpoint')), toolboxList);
+    bValidDir = ~bInvalidName & (vertcat(toolboxList.isdir) > 0);
+    toolboxList = toolboxList(bValidDir);
+    
+    % Always add local/shared directory to matlab 
+    toolboxMap('MATLAB') = {fullfile(toolboxRoot,'local');fullfile(toolboxRoot,'shared')};
+    
+    for i=1:length(toolboxList)
+        verStruct = ver(toolboxList(i).name);
+        if ( isempty(verStruct) )
+            continue;
+        end
+        
+        if ( isKey(toolboxMap,verStruct.Name) )
+            toolboxMap(verStruct.Name) = [toolboxMap(verStruct.Name); {fullfile(toolboxRoot,toolboxList(i).name)}];
+        else
+            toolboxMap(verStruct.Name) = {fullfile(toolboxRoot,toolboxList(i).name)};
+        end
+    end
+end
diff --git a/src/MATLAB/+Dev/GetCommitInfo.m b/src/MATLAB/+Dev/GetCommitInfo.m
new file mode 100644
index 0000000000000000000000000000000000000000..cdfc4bbf7e354d40848a01ca698b25ccc50636f3
--- /dev/null
+++ b/src/MATLAB/+Dev/GetCommitInfo.m
@@ -0,0 +1,15 @@
+% [repoName,commitHash] = GetCommitInfo(inPath)
+%
+
+function [repoName,commitHash] = GetCommitInfo(inPath)
+    remoteUrl = Dev.GitRemote(inPath,'origin');
+    
+    repoName = '';
+    
+    tokMatch = regexp(remoteUrl,'\w+@[\w\-.]+:[\w\-.]+/([\w\-]+\.git)', 'tokens','once');
+    if ( ~isempty(tokMatch) )
+        repoName = tokMatch{1};
+    end
+    
+    commitHash = Dev.GitCommitHash(inPath);
+end
diff --git a/src/MATLAB/+Dev/GetVersion.m b/src/MATLAB/+Dev/GetVersion.m
new file mode 100644
index 0000000000000000000000000000000000000000..e563019b7f900a6f58297480871853d1d86796fd
--- /dev/null
+++ b/src/MATLAB/+Dev/GetVersion.m
@@ -0,0 +1,59 @@
+% version = GetVersion(command)
+% Get a version string or number depending on the command argument. This
+% m-file serves as a single repository for all version-related information.
+%
+% Note: Uses autogenerated version information in +Dev\VersionInfo.m
+
+function versionStr = GetVersion(command)
+    versionStr = [];
+    
+    verInfo = Dev.LoadVersion();
+    
+    if ( ~exist('command','var') )
+        command = 'string';
+    end
+    
+    % Get rid of slashes in branch names
+    cleanBranch  = strrep(verInfo.branchName, '/', '_');
+    cleanBranch  = strrep(cleanBranch, '\', '_');
+    
+    primaryHash = '';
+    matchTok = regexp(verInfo.commitHash{1},'.*:\s*(\w+)','tokens','once');
+    if ( ~isempty(matchTok) )
+        primaryHash = matchTok{1};
+    end
+    
+    if ( strcmpi(command, 'string') || strcmpi(command, 'versionString') )
+        versionStr = [num2str(verInfo.majorVersion) '.' num2str(verInfo.minorVersion) ' ' cleanBranch];
+        return;
+    end
+    
+    if ( strcmpi(command, 'buildString') )
+        versionStr = [verInfo.buildNumber '/' verInfo.buildMachine];
+        return;
+    end
+
+    if ( strcmpi(command, 'primaryHash') )
+        versionStr = primaryHash;
+        return
+    end
+    
+    if ( strcmpi(command, 'buildHashes') )
+        versionStr = verInfo.commitHash;
+        return
+    end
+    
+    if ( strcmpi(command, 'fullString') )
+        versionStr = [verInfo.name ' v' num2str(verInfo.majorVersion) '.' num2str(verInfo.minorVersion) ' ' verInfo.buildNumber '/' verInfo.buildMachine ' ' cleanBranch ' ' primaryHash];
+        return;
+    end
+    
+    if ( strcmpi(command, 'file') )
+        minorStr = num2str(verInfo.minorVersion);
+        minorStr = strrep(minorStr, '.', '_');
+        versionStr = [num2str(verInfo.majorVersion) minorStr '_' cleanBranch];
+        return;
+    end
+    
+end
+
diff --git a/src/MATLAB/+Dev/GitCommitHash.m b/src/MATLAB/+Dev/GitCommitHash.m
new file mode 100644
index 0000000000000000000000000000000000000000..3548ce2344b3b9837be6440a1db0a9e64ac45246
--- /dev/null
+++ b/src/MATLAB/+Dev/GitCommitHash.m
@@ -0,0 +1,16 @@
+function commitHash = GitCommitHash(inPath)
+    bGit = Dev.SetupGit();
+    if ( ~bGit )
+        return;
+    end
+    
+    oldDir = cd(inPath);
+    cleanupObj = onCleanup(@()(cd(oldDir)));
+    
+    [status,hashOut] = system('git rev-parse HEAD');
+    if ( status ~= 0 )
+        return;
+    end
+    
+    commitHash = strtrim(hashOut);
+end
diff --git a/src/MATLAB/+Dev/GitRemote.m b/src/MATLAB/+Dev/GitRemote.m
new file mode 100644
index 0000000000000000000000000000000000000000..fae8a87be8ac2859aceb2d211cfba370d7573a6a
--- /dev/null
+++ b/src/MATLAB/+Dev/GitRemote.m
@@ -0,0 +1,18 @@
+function remoteURL = GitRemote(inPath,remoteName)
+    remoteURL = '';
+    
+    bGit = Dev.SetupGit();
+    if ( ~bGit )
+        return;
+    end
+    
+    oldDir = cd(inPath);
+    cleanupObj = onCleanup(@()(cd(oldDir)));
+    
+    [status,urlOut] = system(['git remote get-url ' remoteName]);
+    if ( status ~= 0 )
+        return;
+    end
+    
+    remoteURL = strtrim(urlOut);
+end
diff --git a/src/MATLAB/+Dev/GitStatus.m b/src/MATLAB/+Dev/GitStatus.m
new file mode 100644
index 0000000000000000000000000000000000000000..90f0693283146a3e3a542455480a64b3d7be004f
--- /dev/null
+++ b/src/MATLAB/+Dev/GitStatus.m
@@ -0,0 +1,67 @@
+function changeLines = GitStatus(chkPath,chkFiles)
+    changeLines = {};
+    
+    if ( ~exist('chkFiles','var') )
+        chkFiles = {};
+    end
+    
+    bGit = Dev.SetupGit();
+    if ( ~bGit )
+        return;
+    end
+    
+    oldDir = cd(chkPath);
+    cleanupObj = onCleanup(@()(cd(oldDir)));
+    
+    [status,res] = system('git status -uall --porcelain');
+    if ( status ~= 0 )
+        return;
+    end
+    
+    statusLines = strsplit(res,'\n').';
+    
+    bValid = cellfun(@(x)(~isempty(x)),statusLines);
+    statusLines = statusLines(bValid);
+    
+    changeTypes = cellfun(@(x)(8*changeMap(x(1)) + changeMap(x(2))), statusLines);
+    changeFiles = cellfun(@(x)(x(4:end)), statusLines, 'UniformOutput',false);
+    
+    if ( isempty(chkFiles) )
+        changeLines = changeFiles(changeTypes > 0);
+        return;
+    end
+    
+    changeLines = {};
+    
+    regexpFiles = regexptranslate('escape', strrep(chkFiles, '\','/'));
+    for i=1:length(changeFiles)
+        matchStarts = regexp(changeFiles{i},regexpFiles, 'start','once');
+        bMatched = cellfun(@(x)(~isempty(x)), matchStarts);
+        
+        if ( any(bMatched) )
+            changeLines = [changeLines; changeFiles(i)];
+        end
+    end
+    
+    extCell = arrayfun(@(x)([ '.\' x.ext '|']), mexext('all'), 'UniformOutput',false);
+    extStr = [extCell{:}];
+    bMexFiles = cellfun(@(x)(~isempty(x)), regexp(chkFiles,extStr(1:end-1),'once'));
+    if ( any(bMexFiles) )
+        matchStarts = regexp(changeFiles,'^src/c/', 'start','once');
+        bMatched = cellfun(@(x)(~isempty(x)), matchStarts);
+        
+        cFiles = changeFiles(bMatched);
+        for i=1:length(cFiles)
+            changeLines = [changeLines; {'Possible MEX dependency - ' cFiles{i}}];
+        end
+    end
+end
+
+function change = changeMap(c)
+    changeList = ['?','M','A','D'];
+    
+    change = find(c == changeList);
+    if ( isempty(change) )
+        change = 0;
+    end
+end
diff --git a/src/MATLAB/+Dev/InitCompiler.m b/src/MATLAB/+Dev/InitCompiler.m
new file mode 100644
index 0000000000000000000000000000000000000000..2a96f76da37295dd174aa6e37fd1775539051bf9
--- /dev/null
+++ b/src/MATLAB/+Dev/InitCompiler.m
@@ -0,0 +1,134 @@
+function initStruct = InitCompiler(productName,forceVersion)
+    if ( ~exist('forceVersion','var') )
+        forceVersion = '';
+    end
+    
+    rootDir = pwd();
+    
+    initStruct = [];
+    
+    %% Build dependency graph for current directory
+    depGraph = Dev.BuildDependencyGraph(rootDir,true);
+
+    %% Get list of matlab toolbox dependencies
+    toolboxRoot = toolboxdir('');
+    bMatlab = strncmp(toolboxRoot,depGraph.nodes, length(toolboxRoot));
+
+    toolboxNodes = depGraph.nodes(bMatlab);
+    toolboxList = Dev.LoadToolboxes(toolboxNodes);
+
+    %% Remove matlab paths/dependencies
+    depGraph.nodes = depGraph.nodes(~bMatlab);
+    depGraph.graph = depGraph.graph(~bMatlab,~bMatlab);
+    
+    %% Get rootPaths and local subdirectories for dependencies
+    [rootPaths,rootNames,localPaths] = Dev.SplitDependencyNames(depGraph.nodes);
+    
+    %% Verify no uncommited changes on dependencies
+    changeString = {};
+    [chkPaths,ia,ic] = unique(rootPaths);
+    chkNames = rootNames(ia);
+    for i=1:length(chkPaths)
+        bInPath = (ic == i);
+        changeLines = Dev.GitStatus(chkPaths{i},localPaths(bInPath));
+        
+        if ( ~isempty(changeLines) )
+            changeString = [changeString; {[chkNames{i} ' (' chkPaths{i} '):']}];
+            for j=1:length(changeLines)
+                changeString = [changeString; {['    ' changeLines{j}]}];
+            end
+        end
+    end
+    
+    if ( ~isempty(changeString) )
+        message = sprintf('The following dependencies have uncommitted changes, are you sure you wish to continue?\n\n');
+        for i=1:length(changeString)
+            message = [message sprintf('%s\n',changeString{i})];
+        end
+        
+        answer = questdlg(message, 'Uncommitted changes!', 'Continue','Cancel', 'Cancel');
+        if ( strcmpi(answer,'Cancel') )
+            initStruct = [];
+            return;
+        end
+    end
+    
+    %% Make full version string and fallback version file
+    Dev.MakeVersion(productName,forceVersion,chkPaths);
+
+    %% Copy the external dependencies to local paths
+    bExternal = ~strncmp(rootDir,rootPaths,length(rootDir));
+    externalPaths = rootPaths(bExternal);
+    externalDeps = localPaths(bExternal);
+    
+    copyPaths = {};
+    for i=1:length(externalPaths)
+        localDir = fileparts(externalDeps{i});
+        if ( ~exist(fullfile(rootDir,localDir),'dir') )
+            mkdir(fullfile(rootDir,localDir));
+        end
+        
+        copyPaths = [copyPaths; fullfile(rootDir,externalDeps{i})];
+        copyfile(fullfile(externalPaths{i},externalDeps{i}), fullfile(rootDir,externalDeps{i}));
+    end
+    
+    initStruct.toolboxList = toolboxList;
+    initStruct.cleanupObj = onCleanup(@()(compilerCleanup(copyPaths)));
+    
+    % temporarily remove any startup scripts that would normally be run by matlabrc
+    enableStartupScripts(false);
+end
+
+function compilerCleanup(copyPaths)
+    % Remove all copied dependencies
+    for i=1:length(copyPaths)
+        if ( exist(copyPaths{i},'file') )
+            delete(copyPaths{i});
+        end
+    end
+    
+    % Re-enable any disabled startup scripts
+    enableStartupScripts(true);
+end
+
+function enableStartupScripts(bEnable)
+    searchPrefix = '';
+    renamePrefix = 'disabled_';
+    if ( bEnable )
+        searchPrefix = 'disabled_';
+        renamePrefix = '';
+    end
+    
+    searchName = [searchPrefix 'startup.m'];
+    newName = [renamePrefix 'startup.m'];
+    
+    startupScripts = findFilesInPath(searchName, userpath);
+    for i=1:length(startupScripts)
+        scriptPath = fileparts(startupScripts{i});
+        movefile(startupScripts{i}, fullfile(scriptPath,newName));
+    end
+end
+
+function fullNames = findFilesInPath(filename, searchPaths)
+    fullNames = {};
+    
+    chkPaths = [];
+    while ( ~isempty(searchPaths) )
+        [newPath remainder] = strtok(searchPaths, pathsep);
+        if ( isempty(newPath) )
+            searchPaths = remainder;
+            continue;
+        end
+        
+        chkPaths = [chkPaths; {newPath}];
+        searchPaths = remainder;
+    end
+    
+    for i=1:length(chkPaths)
+        chkFullPath = fullfile(chkPaths{i}, filename);
+        if ( exist(chkFullPath, 'file') )
+            fullNames = [fullNames; {chkFullPath}];
+        end
+    end
+end
+
diff --git a/src/MATLAB/+Dev/LoadToolboxes.m b/src/MATLAB/+Dev/LoadToolboxes.m
new file mode 100644
index 0000000000000000000000000000000000000000..5f93c35aff9dfce6dfde80b07b7da5f306709a32
--- /dev/null
+++ b/src/MATLAB/+Dev/LoadToolboxes.m
@@ -0,0 +1,15 @@
+function toolboxPaths = LoadToolboxes(toolboxNodes)
+    toolboxMap = Dev.BuildToolboxMap();
+    
+    toolboxRoot = toolboxdir('');
+    toolboxNames = cellfun(@(x)(x(length(toolboxRoot)+2:end)), toolboxNodes, 'UniformOutput',false);
+    
+    toolboxPaths = {};
+    for i=1:length(toolboxNames)
+        if ( ~isKey(toolboxMap,toolboxNames{i}) )
+            continue;
+        end
+        
+        toolboxPaths = [toolboxPaths; toolboxMap(toolboxNames{i})];
+    end
+end
diff --git a/src/MATLAB/+Dev/LoadVersion.m b/src/MATLAB/+Dev/LoadVersion.m
new file mode 100644
index 0000000000000000000000000000000000000000..e9a39d08dd39d0c0665ebcfa7d7695c924ef481f
--- /dev/null
+++ b/src/MATLAB/+Dev/LoadVersion.m
@@ -0,0 +1,82 @@
+function verInfo = LoadVersion()
+    %% Use compiled VersionInfo function when deployed
+    if ( isdeployed )
+        verInfo = Dev.VersionInfo();
+        return;
+    end
+    
+    %% Try to load version tag and branch using git
+    bFoundGit = Dev.SetupGit();
+    verInfo = [];
+    if ( bFoundGit )
+        chkVerInfo = gitVersionInfo();
+    end
+    
+    %% Use fallback file for name (set by Dev.MakeVersion)
+    fallbackFile = 'version.json';
+    
+    fallbackVerInfo = loadFallbackInfo(fallbackFile);
+    if ( isempty(fallbackVerInfo) )
+        fprintf('WARNING: Invalid fallback file, unable to load version information.\n');
+        return;
+    end
+    
+    if ( isempty(chkVerInfo) )
+        fprintf('WARNING: Could not find git directory, using fallback %s\n', fallbackFile);
+        chkVerInfo = fallbackVerInfo;
+    end
+    
+    % Always use the name from fallback file
+    chkVerInfo.name = fallbackVerInfo.name;
+    
+    verInfo = chkVerInfo;
+end
+
+%% Read fallback json version file
+function verInfo = loadFallbackInfo(fallbackFile)
+    verInfo = [];
+    
+    fid = fopen(fallbackFile);
+    if ( fid <= 0 )
+        return;
+    end
+        
+    jsonVer = fread(fid, '*char').';
+    fclose(fid);
+    
+    verInfo = Utils.ParseJSON(jsonVer);
+end
+
+%% Use git tags to get version information
+function verInfo = gitVersionInfo()
+    verInfo = [];
+    
+    [verStatus,verString] = system('git describe --tags --match v[0-9]*.[0-9]* --abbrev=0');
+    [branchStatus,branchString] = system('git rev-parse --abbrev-ref HEAD');
+    
+    [majorVer,minorVer] = Dev.ParseVerTag(verString);
+
+    if ( verStatus ~= 0 || isempty(majorVer) || isempty(minorVer) )
+        fprintf('WARNING: There was an error retrieving tag from git:\n %s\n', verString);
+        return;
+    end
+
+    branchName = strtrim(branchString);
+    if ( branchStatus ~= 0 )
+        fprintf('WARNING: There was an error retrieving branch name from git:\n %s\n', branchName);
+        return;
+    end
+    
+    [repoName,commitHash] = Dev.GetCommitInfo(pwd());
+    commitString = [repoName ' : ' commitHash];
+    
+    %% VersionInfo default structure
+    verInfo = struct(...
+        'name',{repoName},...
+        'majorVersion',{majorVer},...
+        'minorVersion',{minorVer+1},...
+        'branchName',{branchName},...
+        'buildNumber',{'UNKNOWN'},...
+        'buildMachine',{'UNKNOWN'},...
+        'commitHash',{{commitString}});
+end
diff --git a/src/MATLAB/+Dev/MakeVersion.m b/src/MATLAB/+Dev/MakeVersion.m
new file mode 100644
index 0000000000000000000000000000000000000000..4c91fd3699249b5c4a8ba7d88fa671ac331a8a11
--- /dev/null
+++ b/src/MATLAB/+Dev/MakeVersion.m
@@ -0,0 +1,107 @@
+
+
+function verInfo = MakeVersion(productName, forceVersion, dependencyPaths)
+
+    %% Template for the Dev.VersionInfo command
+    funcString = {
+        '%% versionInfo = VersionInfo()'
+        '%% Return the version info structure'
+        '%%'
+        '%% Note: This file is autogenerated by build script DO NOT MODIFY!!'
+        ''
+        'function versionInfo = VersionInfo()'
+        '    jsonVer = ''%s'';'
+        '    '
+        '    versionInfo = Utils.ParseJSON(jsonVer);'
+        'end'};
+    
+    if ( ~exist('forceVersion', 'var') )
+        forceVersion = '';
+    end
+    
+    if ( ~exist('dependencyPaths', 'var') )
+        dependencyPaths = {};
+    end
+    
+    fallbackFile = 'version.json';
+    
+    %% Load version info from git tags
+    verInfo = Dev.LoadVersion();
+    if ( isempty(verInfo) )
+        error('Unable to load git version information');
+    end
+    
+    % Force name to be the passed in product name
+    verInfo.name = productName;
+    
+    %% If we are forcing a specific version number
+    if ( ~isempty(forceVersion) )
+        verString = ['v' forceVersion];
+        
+        [majorVer,minorVer] = Dev.ParseVerTag(verString);
+        if ( isempty(majorVer) )
+            error('Invalid version string %s', verString);
+        end
+        
+        verInfo.majorVersion = majorVer;
+        verInfo.minorVersion = minorVer;
+    end
+    
+    %% Get a timestamp build-number
+    c = clock();
+    verInfo.buildNumber = sprintf('%d.%02d.%02d.%02d', c(1), c(2), c(3), c(4));
+    
+    %% Get machine ID
+    [status,buildMachine] = system('hostname');
+    if ( status ~= 0 )
+        fprintf('WARNING: There was an error retrieving hostname:\n %s\n', buildMachine);
+    else
+        verInfo.buildMachine = strtrim(buildMachine);
+    end
+    
+    %% Make sure local path is not in the list
+    localPath = pwd();
+    bHasLocal = strcmp(localPath,dependencyPaths);
+    dependencyPaths = dependencyPaths(~bHasLocal);
+    
+    %% Add all other dependent commit hashes to list
+    hashStrings = cell(length(dependencyPaths),1);
+    for i=1:length(dependencyPaths)
+        [repoName,commitHash] = Dev.GetCommitInfo(dependencyPaths{i});
+        hashStrings{i} = [repoName ' : ' commitHash];
+    end
+    
+    verInfo.commitHash = [verInfo.commitHash; hashStrings];
+    
+    %% Create +Dev/VersionInfo.m for use in compiled files
+    % Concatenate the template function lines into one giant string
+    templateString = [];
+    for i=1:length(funcString)
+        templateString = [templateString funcString{i} '\n'];
+    end
+
+    % Now insert all our arguments into the template and write to a file.
+    if ( ~exist('+Dev','dir') )
+        mkdir('+Dev');
+    end
+    
+    fid = fopen(fullfile('+Dev','VersionInfo.m'), 'wt');
+    if ( fid <= 0 )
+        error('Unable to open +Dev/VersionInfo.m for writing');
+    end
+
+    jsonVer = Utils.CreateJSON(verInfo,false);
+    fprintf(fid, templateString, jsonVer);
+
+    fclose(fid);
+
+    %% Update fallback file if we used git to retrieve version info.
+    jsonStr = Utils.CreateJSON(verInfo);
+    fid = fopen(fallbackFile, 'wt');
+    if ( fid <= 0 )
+        return;
+    end
+
+    fprintf(fid, '%s\n', jsonStr);
+    fclose(fid);
+end
diff --git a/src/MATLAB/+Dev/MergeGraphStruct.m b/src/MATLAB/+Dev/MergeGraphStruct.m
new file mode 100644
index 0000000000000000000000000000000000000000..c8dcab1c81d9c9dea391115a403cf66f7cca8c30
--- /dev/null
+++ b/src/MATLAB/+Dev/MergeGraphStruct.m
@@ -0,0 +1,18 @@
+function [graphStruct,newNodes] = MergeGraphStruct(graphStruct, newStruct)
+    oldIdx = cellfun(@(x)(find(strcmp(x,graphStruct.nodes),1,'first')), newStruct.nodes, 'UniformOutput',0);
+    bNew = cellfun(@(x)(isempty(x)), oldIdx);
+
+    newNodes = newStruct.nodes(bNew);
+    
+    p = length(newNodes);
+    [m,n] = size(graphStruct.graph);
+
+    idxMap = zeros(1,length(newStruct.nodes));
+    idxMap(~bNew) = [oldIdx{~bNew}];
+    idxMap(bNew) = (1:p) + n;
+
+    graphStruct.nodes = [graphStruct.nodes; newNodes];
+    graphStruct.graph = [graphStruct.graph zeros(n,p); zeros(p,n+p)];
+    
+    graphStruct.graph(idxMap,idxMap) = (graphStruct.graph(idxMap,idxMap) | newStruct.graph);
+end
\ No newline at end of file
diff --git a/src/MATLAB/+Dev/ParseVerTag.m b/src/MATLAB/+Dev/ParseVerTag.m
new file mode 100644
index 0000000000000000000000000000000000000000..278ecaa61b879af28761ec7d4c6b4c2fd95031d0
--- /dev/null
+++ b/src/MATLAB/+Dev/ParseVerTag.m
@@ -0,0 +1,15 @@
+% [majorVer,minorVer] = ParseVerTag(verString)
+% 
+
+function [majorVer,minorVer] = ParseVerTag(verString)
+    majorVer = [];
+    minorVer = [];
+    
+    verString = strtrim(verString);
+    
+    numTok = regexp(verString, '[Vv](\d+)\.(\d+(?:\.\d+)?).*', 'tokens', 'once');
+    if ( length(numTok) >= 2 )
+        majorVer = str2double(numTok{1});
+        minorVer = str2double(numTok{2});
+    end
+end
diff --git a/src/MATLAB/+Dev/SetupGit.m b/src/MATLAB/+Dev/SetupGit.m
new file mode 100644
index 0000000000000000000000000000000000000000..aece550b17f59bdfd74b87a7b3614ed789303da7
--- /dev/null
+++ b/src/MATLAB/+Dev/SetupGit.m
@@ -0,0 +1,70 @@
+function bFoundGit = SetupGit()
+    bFoundGit = 0;
+    
+    [bGitError gitVer] = system('git version');
+    if ( ~bGitError )
+        bFoundGit = 1;
+        return;
+    end
+    
+    gitPath = findGitPath();
+    if ( isempty(gitPath) )
+        return;
+    end
+    
+    pathEnv = getenv('PATH');
+    idx = strfind(pathEnv,gitPath);
+    if ( isempty(idx) )
+        pathEnv = [gitPath pathsep pathEnv];
+        setenv('PATH', pathEnv);
+    end
+    
+    [bGitError gitVer] = system('git version');
+    if ( ~bGitError )
+        bFoundGit = 1;
+        return;
+    end
+end
+
+function gitPath = findGitPath()
+    gitPath = '';
+    
+    comparch = computer('arch');
+    progFilesPath64 = 'C:\Program Files';
+    if ( strcmpi(comparch,'win64') )
+        progFilesPath = getenv('ProgramFiles(x86)');
+        progFilesPath64 = getenv('ProgramFiles');
+    elseif ( strcmpi(comparch,'win32') )
+        progFilesPath = getenv('ProgramFiles');
+    else
+        return;
+    end
+    
+    tryPaths = {fullfile(progFilesPath, 'Git');
+                fullfile(progFilesPath64, 'Git');
+                fullfile(progFilesPath, 'msysgit');
+                fullfile(progFilesPath64, 'msysgit');
+                'C:\Git';
+                'C:\msysgit'};
+	
+    trySubdir = {'bin';'cmd'};
+	
+    foundPath = '';
+    for i=1:length(tryPaths)
+        if ( exist(tryPaths{i}, 'dir') )
+            foundPath = tryPaths{i};
+            break;
+        end
+    end
+    
+    if ( isempty(foundPath) )
+        return;
+    end
+    
+    for i=1:length(trySubdir)
+        if ( exist(fullfile(foundPath,trySubdir{i},'git.exe'),'file') || exist(fullfile(foundPath,trySubdir{i},'git.cmd'),'file') )
+            gitPath = fullfile(foundPath,trySubdir{i});
+            return;
+        end
+    end
+end
diff --git a/src/MATLAB/+Dev/SplitDependencyNames.m b/src/MATLAB/+Dev/SplitDependencyNames.m
new file mode 100644
index 0000000000000000000000000000000000000000..6cf65d26a984843f085c59872b023989e11b83e6
--- /dev/null
+++ b/src/MATLAB/+Dev/SplitDependencyNames.m
@@ -0,0 +1,61 @@
+function [rootPath,rootName,localPath] = SplitDependencyNames(depList)
+    rootPath = cell(length(depList),1);
+    rootName = cell(length(depList),1);
+    localPath = cell(length(depList),1);
+    
+    dropPostfix = {'src/MATLAB'};
+    localMatches = '^private$|^\+.+$|^@.+$|^.+\.(m|mexw64|mexw32|fig)$';
+    
+    for i=1:length(depList)
+         qualPath = splitQualifiedPath(depList{i});
+         
+         bLocalPath = cellfun(@(x)(~isempty(x)),regexp(qualPath,localMatches, 'start','once'));
+         endRoot = find(diff(bLocalPath),1,'last');
+         if ( isempty(endRoot) )
+             % This shouldn't happen if matlab toolboxes are handled first.
+             continue;
+         end
+         
+         chkPath = qualPath(1:endRoot);
+         for j=1:size(dropPostfix,1)
+             chkPostfix = strsplit(dropPostfix{j},'/');
+             cmpIdx = length(chkPath)-length(chkPostfix) + 1;
+             bDropMatch = strcmp(chkPostfix,chkPath(cmpIdx:end));
+             
+             if ( all(bDropMatch) )
+                 chkPath = chkPath(1:(cmpIdx-1));
+                 break;
+             end
+         end
+         
+         rootName{i} = chkPath{end};
+         rootPath{i} = fullfile(qualPath{1:endRoot});
+         localPath{i} = fullfile(qualPath{(endRoot+1):end});
+    end
+end
+
+function qualPath = splitQualifiedPath(inPath)
+    chkPath = strrep(inPath, '\','/');
+    
+    qualPath = {};
+    splitRoot = regexp(chkPath,'^(/{1,2}.+?|.+?:)/(.*?)$','tokens','once');
+    if ( ~isempty(splitRoot) )
+        qualPath = splitRoot(1);
+        chkPath = splitRoot{2};
+    end
+    
+    splitPath = strsplit(chkPath,'/');
+    while ( ~isempty(splitPath) )
+        popPath = splitPath{1};
+        splitPath = splitPath(2:end);
+        
+        if ( strcmp('.',popPath) )
+            continue;
+        elseif ( strcmp('..',popPath) )
+            qualPath = qualPath(1:end-1);
+            continue;
+        end
+        
+        qualPath = [qualPath {popPath}];
+    end
+end
diff --git a/src/MATLAB/+ImUtils/ConvertType.m b/src/MATLAB/+ImUtils/ConvertType.m
new file mode 100644
index 0000000000000000000000000000000000000000..01756425f863f3ede570b0939b441431438f33d2
--- /dev/null
+++ b/src/MATLAB/+ImUtils/ConvertType.m
@@ -0,0 +1,117 @@
+% [ imageOut ] = ConvertType(IMAGEIN, OUTCLASS, NORMALIZE)
+% ConvertType converts image from current type into the specified type
+% OUTCLASS
+% If normalize==true then each channel/frame will be set between [0,1] prior
+% to conversion, meaning that normalization happens on a frame by frame as
+% well as a channel by channel bases.
+% Assumes a 5D image of (rows,col,z,channels,time). Non-existent dimensions
+% should be singleton.
+
+function [ imageOut ] = ConvertType(imageIn, typ, normalize)
+
+if (~exist('normalize','var') || isempty(normalize))
+    normalize = 0;
+end
+
+if (~strcmpi(typ,'logical'))
+    imageOut = zeros(size(imageIn),typ);
+else
+    imageOut = false(size(imageIn));
+end
+
+if (normalize)
+    for t=1:size(imageIn,5)
+        for c=1:size(imageIn,4)
+            inType = class(imageIn);
+            if (strcmpi(inType,'double') || strcmpi(inType,'uint64') || strcmpi(inType,'int64'))
+                imTemp = double(imageIn(:,:,:,c,t));
+            else
+                imTemp = single(imageIn(:,:,:,c,t));
+            end
+            imTemp = imTemp-min(imTemp(:));
+            imTemp = imTemp./max(imTemp(:));
+            
+            switch typ
+                case 'uint8'
+                    imageOut(:,:,:,c,t) = im2uint8(imTemp);
+                case 'uint16'
+                    imageOut(:,:,:,c,t) = im2uint16(imTemp);
+                case 'int16'
+                    imageOut(:,:,:,c,t) = im2int16(imTemp);
+                case 'uint32'
+                    imageOut(:,:,:,c,t) = im2uint32(imTemp);
+                case 'int32'
+                    imageOut(:,:,:,c,t) = im2int32(imTemp);
+                case 'single'
+                    imageOut(:,:,:,c,t) = im2single(imTemp);
+                case 'double'
+                    imageOut(:,:,:,c,t) = imTemp;
+                case 'logical'
+                    imageOut(:,:,:,c,t) = imTemp>min(imTemp(:));
+                otherwise
+                    error('Unkown type of image to convert to!');
+            end
+        end
+    end
+else
+    w = whos('imageIn');
+    switch w.class
+        case 'single'
+            imageIn = convertToMaxOfOne(imageIn,w.class);
+        case 'double'
+            imageIn = convertToMaxOfOne(imageIn,w.class);
+    end
+    if (strcmpi(w.class,typ))
+        imageOut = imageIn;
+    else
+        switch typ
+            case 'uint8'
+                imageOut = im2uint8(imageIn);
+            case 'uint16'
+                imageOut = im2uint16(imageIn);
+            case 'int16'
+                imageOut = im2int16(imageIn);
+            case 'uint32'
+                imageOut = im2uint32(imageIn);
+            case 'int32'
+                imageOut = im2int32(imageIn);
+            case 'single'
+                imageOut = im2single(imageIn);
+            case 'double'
+                imageOut = im2double(imageIn);
+            case 'logical'
+                imageOut = imageIn>min(imageIn(:));
+            otherwise
+                error('Unkown type of image to convert to!');
+        end
+    end
+end
+end
+
+function im = convertToMaxOfOne(im,outTyp)
+switch outTyp
+    case 'uint8'
+        im = im./2^8;
+    case 'uint16'
+        if (max(im(:))<2^12+1)
+            im = im./2^12;
+        else
+            im = im./2^16;
+        end
+    case 'int16'
+        im = im./2^15-1;
+    case 'uint32'
+        im = im./2^32;
+    case 'int32'
+        im = im./2^32-1;
+    case 'single'
+        im = im./max(im(:));
+    case 'double'
+        im = im./max(im(:));
+    case 'logical'
+        % im = im;
+    otherwise
+        error('Unkown type of image to convert to!');
+end
+end
+
diff --git a/src/MATLAB/+MicroscopeData/+Helper/CreateUniqueWordedPath.m b/src/MATLAB/+MicroscopeData/+Helper/CreateUniqueWordedPath.m
new file mode 100644
index 0000000000000000000000000000000000000000..80c66aa3ba2f292c38e7c5f95da703710b5387f8
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Helper/CreateUniqueWordedPath.m
@@ -0,0 +1,64 @@
+function newFilePath = CreateUniqueWordedPath(fPath)
+%newFilePath = CreateUniqueWordedPath(fPath)
+% Pass in a path with at least two directories with '\' seperating the
+% direcories, and a new path will be generated that removes the words that
+% are repeated.  This forces the unique words closer to the file name or
+% most deep directory.
+
+driveIdx = find(fPath==':',1,'first');
+if (~isempty(driveIdx))
+    tmpPath = strtrim(MicroscopeData.Helper.SanitizeString(fPath(driveIdx+1:end)));
+    fPath = [fPath(1:driveIdx),tmpPath];
+else
+    fPath = strtrim(MicroscopeData.Helper.SanitizeString(fPath));
+end
+
+dirs = strsplit(fPath,'\');
+
+newDirs = {};
+for i=1:length(dirs)
+    if (strcmp(dirs{i},'.'))
+        continue
+    end
+    if (strcmp(dirs{i},'..'))
+        newDirs = newDirs(1:end-1);
+        continue
+    end
+    if (isempty(newDirs))
+        newDirs = dirs(i);
+    else
+        newDirs{end+1} = dirs{i};
+    end
+end
+
+dirs = newDirs;
+
+wordList = strsplit(dirs{end},' ');
+
+newFilePath = dirs{end};
+
+for i=length(dirs)-1:-1:1
+    curDir = dirs{i};
+    curWords = strsplit(curDir,' ');
+    keepWords = true(1,length(curWords));
+    for j=1:length(curWords)
+        if (any(strcmpi(curWords{j},wordList)))
+            keepWords(j) = false;
+        else
+            wordList{end+1} = curWords{j};
+        end
+    end
+    
+    newDir = '';
+    for j=1:length(keepWords)
+        if (keepWords(j))
+            if (isempty(newDir))
+                newDir = strtrim(curWords{j});
+            else
+                newDir = sprintf('%s %s',newDir,strtrim(curWords{j}));
+            end
+        end
+    end
+    newFilePath = fullfile(newDir,newFilePath);
+end
+end
diff --git a/src/MATLAB/+MicroscopeData/+Helper/GetPixelTypeTIF.m b/src/MATLAB/+MicroscopeData/+Helper/GetPixelTypeTIF.m
new file mode 100644
index 0000000000000000000000000000000000000000..25247b9e1d4018aa97238dfa3930e8fe61a2fabf
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Helper/GetPixelTypeTIF.m
@@ -0,0 +1,37 @@
+% [pixelType,dataTypeLookup] = GetPixelTypeTIF(tifFile)
+
+function [pixelType,imInfo] = GetPixelTypeTIF(tifFile)
+    pixelType = [];
+    
+    dataTypeLookup = {'uint8';'uint16';'uint32';'uint64';
+                  'int8';'int16';'int32';'int64';
+                  'single';'double';
+                  'logical'};
+
+    dataTypeSize = [1;2;4;8;
+                    1;2;4;8;
+                    4;8;
+                    1];
+
+    dataTypeFormat = {'Unsigned integer';'Unsigned integer';'Unsigned integer';'Unsigned integer';
+                        'Integer';'Integer';'Integer';'Integer';
+                        'IEEE floating point';'IEEE floating point';
+                        'Unsigned Integer'};
+    
+    imInfo = imfinfo(tifFile,'tif');
+    sampleFormat = 'Unsigned integer';
+    if ( isfield(imInfo,'SampleFormat') )
+        sampleFormat = imInfo.SampleFormat;
+    end
+    bitDepth = imInfo.BitDepth;
+    
+    bSizeMatch = (dataTypeSize == floor(bitDepth/8));
+    bSampleMatch = strcmpi(sampleFormat,dataTypeFormat);
+    
+    formatIdx = find(bSizeMatch & bSampleMatch);
+    if ( isempty(formatIdx) )
+        return;
+    end
+    
+    pixelType = dataTypeLookup{formatIdx};
+end
diff --git a/src/MATLAB/+MicroscopeData/+Helper/ParseReaderInputs.m b/src/MATLAB/+MicroscopeData/+Helper/ParseReaderInputs.m
new file mode 100644
index 0000000000000000000000000000000000000000..3e70080448e0f95ba717fd9cd8148fa7ab9b1eb8
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Helper/ParseReaderInputs.m
@@ -0,0 +1,43 @@
+function argStruct = ParseReaderInputs(varargin)
+    dataTypeLookup = {'uint8';'uint16';'uint32';'uint64';
+                  'int8';'int16';'int32';'int64';
+                  'single';'double';
+                  'logical'};
+
+    dataTypeSize = [1;2;4;8;
+                    1;2;4;8;
+                    4;8;
+                    1];
+
+    p = inputParser();
+    p.StructExpand = false;
+
+    % This is ridiculous, but we assume that the optional path is specified if
+    % length(varargin) is odd
+    if ( mod(length(varargin),2) == 1 )
+        addOptional(p,'path','', @ischar);
+    else
+        addParameter(p,'path','', @ischar);
+    end
+
+    addParameter(p,'imageData',[], @(x)(validOrEmpty(@isstruct,x)));
+
+    addParameter(p,'chanList',[], @(x)(validOrEmpty(@isvector,x)));
+    addParameter(p,'timeRange',[], @(x)(validOrEmpty(@(y)(numel(y)==2),x)));
+    addParameter(p,'roi_xyz',[], @(x)(validOrEmpty(@(y)(all(size(y)==[2,3])),x)));
+
+    addParameter(p,'outType',[], @(x)(validOrEmpty(@(y)(any(strcmp(y,dataTypeLookup))),x)));
+    addParameter(p,'normalize',false,@islogical);
+
+    addParameter(p,'verbose',false, @islogical);
+    addParameter(p,'prompt',[], @(x)(validOrEmpty(@islogical,x)));
+    addParameter(p,'promptTitle','', @ischar);
+
+    parse(p,varargin{:});
+    argStruct = p.Results;
+end
+
+% Inputs are valid if they are empty or if they satisfy their validity function
+function bValid = validOrEmpty(validFunc,x)
+    bValid = (isempty(x) || validFunc(x));
+end
diff --git a/src/MATLAB/+MicroscopeData/+Helper/SanitizeString.m b/src/MATLAB/+MicroscopeData/+Helper/SanitizeString.m
new file mode 100644
index 0000000000000000000000000000000000000000..43140f0e05b44030b01a2b61922b48b60c6ff507
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Helper/SanitizeString.m
@@ -0,0 +1,24 @@
+function [ str ] = SanitizeString( str )
+%SANITIZESTRING Summary of this function goes here
+%   Detailed explanation goes here
+str = strrep(str, '!', '');
+str = strrep(str, '*', '');
+str = strrep(str, '''', '');
+str = strrep(str, '(', '');
+str = strrep(str, ')', '');
+str = strrep(str, ';', '');
+str = strrep(str, ':', '');
+str = strrep(str, '@', '');
+str = strrep(str, '&', '');
+str = strrep(str, '=', '');
+str = strrep(str, '+', '');
+str = strrep(str, '$', '');
+str = strrep(str, ',', '');
+str = strrep(str, '/', '');
+str = strrep(str, '?', '');
+str = strrep(str, '#', '');
+str = strrep(str, '[', '');
+str = strrep(str, ']', '');
+
+str = strtrim(str);
+end
diff --git a/src/MATLAB/+MicroscopeData/+Original/+BioFormats/CheckJarPath.m b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/CheckJarPath.m
new file mode 100644
index 0000000000000000000000000000000000000000..42ae0e4ee556b23c135446e4ecbc4693ab8ad5d8
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/CheckJarPath.m
@@ -0,0 +1,24 @@
+function CheckJarPath()
+%CHECKJARPATH Summary of this function goes here
+%   Detailed explanation goes here
+
+%% ensure that the bioformats jar file is on the path
+dynamicPaths = javaclasspath('-dynamic');
+bfIsLoaded = false;
+if (~isempty(dynamicPaths))
+    for i=1:length(dynamicPaths)
+        [~,name,~] = fileparts(dynamicPaths{i});
+        if (strcmpi('bioformats_package',name))
+            bfIsLoaded = true;
+            break
+        end
+    end
+end
+
+if (~bfIsLoaded)
+    curPath = mfilename('fullpath');
+    [pathstr,~,~] = fileparts(curPath);
+    javaaddpath(fullfile(pathstr,'bioformats_package.jar'),'-end');
+end
+end
+
diff --git a/src/MATLAB/+MicroscopeData/+Original/+BioFormats/CheckJavaMemory.m b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/CheckJavaMemory.m
new file mode 100644
index 0000000000000000000000000000000000000000..92a2223060a7723abedfcaf22a295809a3ecb124
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/CheckJavaMemory.m
@@ -0,0 +1,54 @@
+function [] = bfCheckJavaMemory(varargin)
+% bfCheckJavaMemory warn if too little memory is allocated to Java
+%
+% SYNOPSIS  bfCheckJavaMemory()
+%
+% Input
+%
+%   minMemory - (Optional) The minimum suggested memory setting in MB.
+%   Default: 512
+%
+% Output
+%
+%    A warning message is printed if too little memory is allocated.
+
+% OME Bio-Formats package for reading and converting biological file formats.
+%
+% Copyright (C) 2014 Open Microscopy Environment:
+%   - Board of Regents of the University of Wisconsin-Madison
+%   - Glencoe Software, Inc.
+%   - University of Dundee
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as
+% published by the Free Software Foundation, either version 2 of the
+% License, or (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License along
+% with this program; if not, write to the Free Software Foundation, Inc.,
+% 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+runtime = java.lang.Runtime.getRuntime();
+maxMemory = runtime.maxMemory() / (1024 * 1024);
+
+ip = inputParser;
+ip.addOptional('minMemory', 512, @isscalar);
+ip.parse(varargin{:});
+minMemory = ip.Results.minMemory;
+
+warningID = 'BF:lowJavaMemory';
+
+if maxMemory < minMemory - 64
+    warning_msg = [...
+        '*** Insufficient memory detected. ***\n'...
+        '*** %dm found ***\n'...
+        '*** %dm or greater is recommended ***\n'...
+        '*** See http://www.mathworks.com/matlabcentral/answers/92813 ***\n'...
+        '*** for instructions on increasing memory allocation. ***\n'];
+    warning(warningID, warning_msg, round(maxMemory), minMemory);
+end
diff --git a/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetImages.m b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetImages.m
new file mode 100644
index 0000000000000000000000000000000000000000..4ab4ffa76d764824df513dd1e15d88472cff579d
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetImages.m
@@ -0,0 +1,117 @@
+function [ seriesImages ] = GetImages( bfReader, seriesNum )
+%GETIMAGES Summary of this function goes here
+%   Detailed explanation goes here
+
+numSeries = bfReader.getSeriesCount();
+
+omeMetadata = bfReader.getMetadataStore();
+prgs = Utils.CmdlnProgress(1,true);
+
+if (exist('seriesNum','var') && ~isempty(seriesNum) && numSeries>=seriesNum)
+    seriesImages = readSeriesImage(bfReader, seriesNum-1, omeMetadata, true, prgs);
+else
+    if (bfReader.getSeriesCount()>1)
+        prgs.SetMaxIterations(bfReader.getSeriesCount());
+
+        onlyOneSeries = false;
+    else
+        prgs.SetMaxIterations(numSeries);
+        onlyOneSeries = true;
+    end
+
+    for series=0:numSeries-1;
+        im = readSeriesImage(bfReader, series, omeMetadata, onlyOneSeries, prgs);
+
+        seriesImages{series+1} = im;
+
+        prgs.PrintProgress(series+1);
+    end
+end
+
+prgs.ClearProgress();
+end
+
+function im = readSeriesImage(bfReader, series, omeMetadata, onlyOneSeries, prgs)
+    bfReader.setSeries(series);
+
+    imageData = [];
+
+    imageData.Dimensions = [safeGetValue(omeMetadata.getPixelsSizeX(series));...
+                            safeGetValue(omeMetadata.getPixelsSizeY(series));...
+                            safeGetValue(omeMetadata.getPixelsSizeZ(series))];
+
+    imageData.NumberOfChannels = omeMetadata.getChannelCount(series);
+    imageData.NumberOfFrames = safeGetValue(omeMetadata.getPixelsSizeT(series));
+
+    clss = char(omeMetadata.getPixelsType(series));
+    if (strcmpi(clss,'float'))
+        clss = 'single';
+    end
+    im = zeros([Utils.SwapXY_RC(imageData.Dimensions'),imageData.NumberOfChannels,imageData.NumberOfFrames],clss);
+
+    order = char(omeMetadata.getPixelsDimensionOrder(series));
+
+    if (onlyOneSeries)
+        prgs.SetMaxIterations(imageData.NumberOfFrames*imageData.NumberOfChannels*imageData.Dimensions(3));
+        i = 1;
+    end
+
+    for t=1:imageData.NumberOfFrames
+        for z=1:imageData.Dimensions(3)
+            for c=1:imageData.NumberOfChannels
+                ind = calcPlaneInd(order,z,c,t,imageData);
+                im(:,:,z,c,t) = MicroscopeData.Original.BioFormats.GetPlane(bfReader,ind);
+
+                if (onlyOneSeries)
+                    prgs.PrintProgress(i);
+                    i = i+1;
+                end
+            end
+        end
+    end
+end
+
+function ind = calcPlaneInd(order,z,c,t,imageData)
+switch order(3)
+    case 'Z'
+        ind = z-1;
+        mul = imageData.Dimensions(3);
+    case 'C'
+        ind = c-1;
+        mul = imageData.NumberOfChannels;
+    case 'T'
+        ind = t-1;
+        mul = imageData.NumberOfFrames;
+end
+
+switch order(4)
+    case 'Z'
+        ind = ind + (z-1)*mul;
+        mul = imageData.Dimensions(3)*mul;
+    case 'C'
+        ind = ind + (c-1)*mul;
+        mul = imageData.NumberOfChannels*mul;
+    case 'T'
+        ind = ind + (t-1)*mul;
+        mul = imageData.NumberOfFrames*mul;
+end
+
+switch order(5)
+    case 'Z'
+        ind = ind + (z-1)*mul;
+    case 'C'
+        ind = ind + (c-1)*mul;
+    case 'T'
+        ind = ind + (t-1)*mul;
+end
+
+ind = ind +1;
+end
+
+function val = safeGetValue(varIn)
+if (isempty(varIn))
+    val = 0;
+    return
+end
+val = varIn.getValue();
+end
diff --git a/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetMetadata.m b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetMetadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..decc62a844aa6268d813654caf1ad3b0808abf02
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetMetadata.m
@@ -0,0 +1,171 @@
+function [seriesMetadata, varargout] = GetMetadata( bfReader, datasetExt )
+%GETMETADATA Summary of this function goes here
+%   Detailed explanation goes here
+
+seriesMetadata = {};
+
+if (~exist('datasetExt','var') || isempty(datasetExt))
+    datasetExt = '';
+end
+
+orgMetadata = bfReader.getSeriesMetadata();
+omeMetadata = bfReader.getMetadataStore();
+
+onlyOneSeries = true;
+if (bfReader.getSeriesCount()>1)
+    prgs = Utils.CmdlnProgress(bfReader.getSeriesCount(),true);
+    onlyOneSeries = false;
+end
+
+for series=0:bfReader.getSeriesCount()-1;
+    bfReader.setSeries(series);
+
+    imageData = [];
+
+    [~,imageData.DatasetName,~] = fileparts(char(omeMetadata.getImageName(series)));
+
+    imageData.Dimensions = [safeGetValue(omeMetadata.getPixelsSizeX(series)),...
+                            safeGetValue(omeMetadata.getPixelsSizeY(series)),...
+                            safeGetValue(omeMetadata.getPixelsSizeZ(series))];
+
+    imageData.NumberOfChannels = omeMetadata.getChannelCount(series);
+    imageData.NumberOfFrames = safeGetValue(omeMetadata.getPixelsSizeT(series));
+
+    xPixelPhysicalSize = safeGetValue(omeMetadata.getPixelsPhysicalSizeX(series));
+    if xPixelPhysicalSize==0
+        xPixelPhysicalSize = 1;
+    end
+
+    yPixelPhysicalSize = safeGetValue(omeMetadata.getPixelsPhysicalSizeY(series));
+    if yPixelPhysicalSize==0
+        yPixelPhysicalSize = 1;
+    end
+
+    zPixelPhysicalSize = safeGetValue(omeMetadata.getPixelsPhysicalSizeZ(series));
+    if zPixelPhysicalSize==0
+        zPixelPhysicalSize = 1;
+    end
+
+    imageData.PixelPhysicalSize = [xPixelPhysicalSize, yPixelPhysicalSize, zPixelPhysicalSize];
+
+    if (strcmp(datasetExt,'.czi'))
+        imageData.Position = [orgMetadata.get('Global Information|Image|S|Scene|Position|X #1'),...
+                              orgMetadata.get('Global Information|Image|S|Scene|Position|Y #1'),...
+                              orgMetadata.get('Global Information|Image|S|Scene|Position|Z #1')];
+    elseif (omeMetadata.getPlaneCount(series)>0)
+        imageData.Position = [double(omeMetadata.getPlanePositionX(series,0)),...
+                              double(omeMetadata.getPlanePositionY(series,0)),...
+                              double(omeMetadata.getPlanePositionZ(series,0))];
+    end
+
+    imageData.ChannelNames = cell(imageData.NumberOfChannels,1);
+    for c=1:imageData.NumberOfChannels
+        colr = char(omeMetadata.getChannelName(series,c-1));
+
+        if (isempty(colr))
+            colr = sprintf('Channel:%d',c);
+        end
+
+        imageData.ChannelNames{c} = colr;
+    end
+
+    imageData.StartCaptureDate = safeGetValue(omeMetadata.getImageAcquisitionDate(series));
+    ind = strfind(imageData.StartCaptureDate,'T');
+    if (~isempty(ind))
+        imageData.StartCaptureDate(ind) = ' ';
+    end
+
+    imageData.TimeStampDelta = 0;
+
+    order = char(omeMetadata.getPixelsDimensionOrder(series));
+
+    if (onlyOneSeries)
+        prgs = Utils.CmdlnProgress(imageData.NumberOfFrames*imageData.NumberOfChannels*imageData.Dimensions(3),true);
+        i = 1;
+    end
+
+    for t=1:imageData.NumberOfFrames
+        for z=1:imageData.Dimensions(3)
+            for c=1:imageData.NumberOfChannels
+                ind = calcPlaneInd(order,z,c,t,imageData);
+                try
+                    delta = omeMetadata.getPlaneDeltaT(series,ind-1);
+                catch er
+                    delta = [];
+                end
+                if (~isempty(delta))
+                    imageData.TimeStampDelta(z,c,t) = delta.floatValue;
+                end
+                if (onlyOneSeries)
+                    prgs.PrintProgress(i);
+                    i = i+1;
+                end
+            end
+        end
+    end
+
+    if (size(imageData.TimeStampDelta,1)~=imageData.Dimensions(3) ||...
+            size(imageData.TimeStampDelta,2)~=imageData.NumberOfChannels || ...
+            size(imageData.TimeStampDelta,3)~=imageData.NumberOfFrames)
+        imageData = rmfield(imageData,'TimeStampDelta');
+    end
+
+    seriesMetadata{series+1} = imageData;
+
+    prgs.PrintProgress(series+1);
+end
+
+prgs.ClearProgress();
+
+if (nargout>1)
+    varargout{1} = omeMetadata;
+end
+if (nargout>2)
+    varargout{2} = orgMetadata;
+end
+end
+
+function ind = calcPlaneInd(order,z,c,t,imageData)
+switch order(3)
+    case 'Z'
+        ind = z-1;
+        mul = imageData.Dimensions(3);
+    case 'C'
+        ind = c-1;
+        mul = imageData.NumberOfChannels;
+    case 'T'
+        ind = t-1;
+        mul = imageData.NumberOfFrames;
+end
+
+switch order(4)
+    case 'Z'
+        ind = ind + (z-1)*mul;
+        mul = imageData.Dimensions(3)*mul;
+    case 'C'
+        ind = ind + (c-1)*mul;
+        mul = imageData.NumberOfChannels*mul;
+    case 'T'
+        ind = ind + (t-1)*mul;
+        mul = imageData.NumberOfFrames*mul;
+end
+
+switch order(5)
+    case 'Z'
+        ind = ind + (z-1)*mul;
+    case 'C'
+        ind = ind + (c-1)*mul;
+    case 'T'
+        ind = ind + (t-1)*mul;
+end
+
+ind = ind +1;
+end
+
+function val = safeGetValue(varIn)
+if (isempty(varIn))
+    val = 0;
+    return
+end
+val = varIn.getValue();
+end
diff --git a/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetPlane.m b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetPlane.m
new file mode 100644
index 0000000000000000000000000000000000000000..8794cc475b4a03b382b45c0a2dbbeb966be96e0e
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetPlane.m
@@ -0,0 +1,110 @@
+function I = GetPlane(r, iPlane, varargin)
+% BFGETPLANE Retrieve the plane data from a reader using Bio-Formats
+% 
+%   I = bfGetPlane(r, iPlane) returns a specified plane from the input
+%   format reader. The index specifying the plane to retrieve should be
+%   contained between 1 and the number of planes for the series.
+%
+%   I = bfGetPlane(r, iPlane, x, y, width, height) only returns the tile
+%   which origin is specified by (x, y) and dimensions are specified by
+%   (width, height).
+%
+% Examples
+%
+%    I = bfGetPlane(r, 1) % First plane of the series
+%    I = bfGetPlane(r, r.getImageCount()) % Last plane of the series
+%    I = bfGetPlane(r, 1, 1, 1, 20, 20) % 20x20 tile originated at (0, 0)
+%
+% See also: BFGETREADER
+
+% OME Bio-Formats package for reading and converting biological file formats.
+%
+% Copyright (C) 2012 - 2014 Open Microscopy Environment:
+%   - Board of Regents of the University of Wisconsin-Madison
+%   - Glencoe Software, Inc.
+%   - University of Dundee
+%
+% This program is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as
+% published by the Free Software Foundation, either version 2 of the
+% License, or (at your option) any later version.
+%
+% This program is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License along
+% with this program; if not, write to the Free Software Foundation, Inc.,
+% 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+% Input check
+ip = inputParser;
+ip.addRequired('r', @(x) isa(x, 'loci.formats.IFormatReader') && ...
+    ~isempty(x.getCurrentFile()));
+ip.parse(r);
+
+% Plane check
+isValidPlane = @(x) isscalar(x) && ismember(x, 1 : r.getImageCount());
+ip.addRequired('iPlane', isValidPlane);
+
+% Optional tile arguments check
+isValidX = @(x) isscalar(x) && ismember(x, 1 : r.getSizeX());
+isValidY = @(x) isscalar(x) && ismember(x, 1 : r.getSizeY());
+ip.addOptional('x', 1, isValidX);
+ip.addOptional('y', 1, isValidY);
+ip.addOptional('width', r.getSizeX(), isValidX);
+ip.addOptional('height', r.getSizeY(), isValidY);
+ip.parse(r, iPlane, varargin{:});
+
+% Additional check for tile size
+assert(ip.Results.x - 1 + ip.Results.width <= r.getSizeX(),...
+     'MATLAB:InputParser:ArgumentFailedValidation',...
+     'Invalid tile size');
+assert(ip.Results.y - 1 + ip.Results.height <= r.getSizeY(),...
+     'MATLAB:InputParser:ArgumentFailedValidation',...
+     'Invalid tile size');
+
+% Get pixel type
+pixelType = r.getPixelType();
+bpp = loci.formats.FormatTools.getBytesPerPixel(pixelType);
+fp = loci.formats.FormatTools.isFloatingPoint(pixelType);
+sgn = loci.formats.FormatTools.isSigned(pixelType);
+little = r.isLittleEndian();
+
+plane = r.openBytes(iPlane - 1, ip.Results.x - 1, ip.Results.y - 1, ...
+    ip.Results.width, ip.Results.height);
+    
+% convert byte array to MATLAB image
+if sgn
+    % can get the data directly to a matrix
+    I = loci.common.DataTools.makeDataArray2D(plane, ...
+        bpp, fp, little, ip.Results.height);
+else
+    % get the data as a vector, either because makeDataArray2D
+    % is not available, or we need a vector for typecast
+    I = loci.common.DataTools.makeDataArray(plane, ...
+        bpp, fp, little);
+end
+
+% Java does not have explicitly unsigned data types;
+% hence, we must inform MATLAB when the data is unsigned
+if ~sgn
+    % NB: arr will always be a vector here
+    switch class(I)
+        case 'int8'
+            I = typecast(I, 'uint8');
+        case 'int16'
+            I = typecast(I, 'uint16');
+        case 'int32'
+            I = typecast(I, 'uint32');
+        case 'int64'
+            I = typecast(I, 'uint64');
+    end
+end
+
+if isvector(I)
+    % convert results from vector to matrix
+    shape = [ip.Results.width ip.Results.height];
+    I = reshape(I, shape)';
+end
diff --git a/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetReader.m b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetReader.m
new file mode 100644
index 0000000000000000000000000000000000000000..80d105a429965219a77bda69109675aa5b95a359
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/+BioFormats/GetReader.m
@@ -0,0 +1,22 @@
+function [ bfReader ] = GetReader( fullPath )
+%GETSERIESDATA Summary of this function goes here
+%   Detailed explanation goes here
+
+%% read data using bioformats
+MicroscopeData.Original.BioFormats.CheckJavaMemory();
+loci.common.DebugTools.enableLogging('INFO');
+
+bfReader = loci.formats.ChannelFiller();
+bfReader = loci.formats.ChannelSeparator(bfReader);
+OMEXMLService = loci.formats.services.OMEXMLServiceImpl();
+bfReader.setMetadataStore(OMEXMLService.createOMEXMLMetadata());
+
+try
+    bfReader.setId(fullPath);
+catch err
+    warning(err.message);
+    bfReader = [];
+end
+
+end
+
diff --git a/src/MATLAB/+MicroscopeData/+Original/CanExportFormat.m b/src/MATLAB/+MicroscopeData/+Original/CanExportFormat.m
new file mode 100644
index 0000000000000000000000000000000000000000..6ad68667b7a1b8a6012d8322de3dc7b913dc4fb8
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/CanExportFormat.m
@@ -0,0 +1,31 @@
+% [bCanExport,guessType] = CanExportFormat(filenames)
+% 
+% bCanExport - True for all filenames that match a bioformats supported extension
+% guessType - Cell arry of strings representing a guess at the associated file type
+
+function [bCanExport,guessType] = CanExportFormat(filenames)
+    exportFormats = MicroscopeData.Original.GetSupportedFormats();
+    formatIdx = arrayfun(@(x,y)(repmat(y,length(x{1}),1)),exportFormats(:,2),(1:size(exportFormats,1)).', 'UniformOutput',false);
+    
+    allExt = vertcat(exportFormats{:,2});
+    allIdx = vertcat(formatIdx{:});
+    
+    [chkExt,idxExt] = unique(allExt);
+    chkIdx = allIdx(idxExt);
+    
+    % Can use strncmpi to quickly check extension matches on reversed filenames
+    revExt = cellfun(@(x)(x(end:-1:1)),chkExt, 'UniformOutput',false);
+    revNames = cellfun(@(x)(x(end:-1:1)),filenames, 'UniformOutput',false);
+    
+    bCanExport = false(length(filenames),1);
+    guessType = cell(length(filenames),1);
+    
+    matchExt = cellfun(@(x)(find(strncmpi(revNames,x,length(x)))),revExt,'UniformOutput',false);
+    matchFormatIdx = arrayfun(@(x,y)(repmat(y,length(x{1}),1)),matchExt,chkIdx,'UniformOutput',false);
+    
+    matchedIdx = vertcat(matchExt{:});
+    matchedFormats = vertcat(matchFormatIdx{:});
+    
+    bCanExport(matchedIdx) = true;
+    guessType(matchedIdx) = exportFormats(matchedFormats,1);
+end
diff --git a/src/MATLAB/+MicroscopeData/+Original/Convert2Tiffs.m b/src/MATLAB/+MicroscopeData/+Original/Convert2Tiffs.m
new file mode 100644
index 0000000000000000000000000000000000000000..b3863dfb33c7f16166d2e484f03195b4f515ddd2
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/Convert2Tiffs.m
@@ -0,0 +1,75 @@
+function [ im, imD ] = Convert2Tiffs( imDir, imName, outDir, overwrite, quiet, cleanName)
+%[ im, imD ] = Convert2Tiffs( imDir, imName, outDir, overwrite, quiet, cleanName)
+
+im = [];
+imD = [];
+
+if (~exist('overwrite','var') || isempty(overwrite))
+    overwrite = false;
+end
+if (~exist('quiet','var') || isempty(quiet))
+    quiet = false;
+end
+
+if (~exist('imDir','var') || isempty(imDir))
+    imDir = '.';
+end
+
+if (~exist('imName','var') || isempty(imName))
+    [imName,imDir,~] = uigetfile('*.*','Choose a Microscope File to Convert');
+    if (imName==0)
+        warning('Nothing read');
+        return
+    end
+end
+
+if (~exist('outDir','var') || isempty(outDir))
+    outDir = uigetdir('.','Choose a folder to output to');
+    if (outDir==0)
+        warning('No where to write!');
+        return
+    end
+end
+
+if (~exist('cleanName','var') || isempty(cleanName))
+    cleanName = false;
+end
+
+if (cleanName)
+    outDir = MicroscopeData.Helper.CreateUniqueWordedPath(outDir);
+end
+
+[~,name,~] = fileparts(imName);
+
+if (~exist(fullfile(outDir,name),'dir') || overwrite)
+    
+    imD = MicroscopeData.Original.ReadMetadata(imDir,imName);
+    if ( isempty(imD) )
+        return;
+    end
+    
+    if (length(imD)>1)
+        [~,datasetName,~] = fileparts(imName);
+        if (cleanName)
+            datasetName = MicroscopeData.Helper.SanitizeString(datasetName);
+        end
+        outDir = fullfile(outDir,datasetName);
+    end
+    
+    % Don't overwrite images that already exist
+    if (exist(fullfile(outDir,imD{1}.DatasetName),'dir') && ~overwrite)
+        return;
+    end
+    
+    im = MicroscopeData.Original.ReadImages(imDir,imName);
+    for i=1:length(imD)
+        if (cleanName)
+            imD{i}.DatasetName = MicroscopeData.Helper.SanitizeString(imD{i}.DatasetName);
+        end
+        if (~exist(fullfile(outDir,imD{i}.DatasetName),'dir') || overwrite)
+            MicroscopeData.Writer(im{i},fullfile(outDir,imD{i}.DatasetName),imD{i},[],[],[],quiet);
+        end
+    end
+end
+end
+
diff --git a/src/MATLAB/+MicroscopeData/+Original/GetSupportedFormats.m b/src/MATLAB/+MicroscopeData/+Original/GetSupportedFormats.m
new file mode 100644
index 0000000000000000000000000000000000000000..aef1a75495a2ff02f6f7858499b0c87a6923fa47
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/GetSupportedFormats.m
@@ -0,0 +1,294 @@
+% This is just a big list of names and associated extensions supported by bioformats; based on their website:
+% https://www.openmicroscopy.org/site/support/bio-formats5.1/supported-formats.html.
+
+function formats = GetSupportedFormats()
+    extensions = {{'.gif'};
+                {'.jpg'};
+                {'.jp2'};
+                {'.png'};
+                {'.txt'};
+                {'.tif';'.tiff'};
+                {'.bmp'};
+                
+                {'.sld'};
+                {'.tif'};
+                {'.aim'};
+                {'.al3d'};
+                {'.gel'};
+                {'.am';'.amiramesh';'.grey';'.hx';'.labels'};
+                {'.cif'};
+                {'.img';'.hdr'};
+                {'.png'};
+                {'.afi';'.svs'};
+                {'.svs'};
+                {'.htd';'.pnl'};
+                {'.avi'};
+                {'.arf'};
+                {'.exp';'.tif'};
+                {'.sdt'};
+                {'.1sc'};
+                {'.pic';'.raw';'.xml'};
+                {'.scn'};
+                {'.ims'};
+                { };
+                {'.img'};
+                {'.cr2';'.crw'};
+                {'.ch5'};
+                {'.c01';'.dib'};
+                {'.vsi'};
+                {'.xml';'.tif'};
+                {'.dv';'.r3d'};
+                {'.dcm';'.dicom'};
+                {'.v'};
+                {'.eps';'.epsi';'.ps'};
+                {'.flex';'.mea';'.res'};
+                {'.img'};
+                {'.tiff'};
+                {'.fits'};
+                {'.dm3';'.dm4'};
+                {'.dm2'};
+                {'.naf'};
+                {'.his'};
+                {'.ndpi';'.ndpis'};
+                {'.vms'};
+                {'.txt';'.tif';'.bmp';'.jpg'};
+                {'.i2i'};
+                {'.ics';'.ids'};
+                {'.fff'};
+                {'.seq'};
+                {'.ipw'};
+                {'.hed';'.img'};
+                {'.mod'};
+                {'.liff'};
+                {'.raw'};
+                {'.tif'};
+                {'.obf';'.msr'};
+                {'.xdce';'.tif'};
+                {'.frm'};
+                {'.inr'};
+                {'.hdr'};
+                {'.ipl'};
+                {'.ipm'};
+                {'.dat';'.img';'.par'};
+                {'.jpk'};
+                {'.jpx'};
+                {'.xv'};
+                {'.bip'};
+                {'.fli'};
+                {'.msr'};
+                {'.lei';'.tif'};
+                {'.lif'};
+                {'.scn'};
+                {'.sxm'};
+                {'.l2d';'.tif';'.scn'};
+                {'.lim'};
+                {'.tiff'};
+                {'.stk';'.nd'};
+                {'.tif'};
+                {'.tif';'.txt';'.xml'};
+                {'.mnc'};
+                {'.mrw'};
+                {'.mng'};
+                {'.stp'};
+                {'.mrc'};
+                {'.nef';'.tif'};
+                {'.img';'.hdr'};
+                {'.tiff'};
+                {'.tiff'};
+                {'.nd2'};
+                {'.nrrd';'.nhdr';'.raw';'.txt'};
+                {'.apl';'.mtb';'.tnb';'.tif';'.obsep'};
+                {'.oib';'.oif'};
+                {'.tif'};
+                {'.xml';'.dat';'.tif'};
+                {'.tiff'};
+                {'.ome.tiff'};
+                {'.ome';'.ome.xml'};
+                {'.top'};
+                {'.pcoraw';'.rec'};
+                {'.pcx'};
+                {'.pds'};
+                {'.im3'};
+                {'.tiff';'.xml'};
+                {'.tif';'.2';'.3';'.4'};
+                {'.pbm';'.pgm';'.ppm'};
+                {'.psd'};
+                {'.tif';'.tiff'};
+                {'.bin'};
+                {'.pict'};
+                {'.tif';'.xml';'.cfg'};
+                {'.afm'};
+                {'.mov'};
+                {'.sm2';'.sm3'};
+                { };
+                {'.xqd';'.xqf'};
+                {'.cxd'};
+                {'.tiff'};
+                { };
+                {'.spi';'.stk'};
+                {'.tga'};
+                {'.vws'};
+                {'.tfr';'.ffr';'.zfr';'.zfp';'.2fl'};
+                {'.tif';'.sld';'.jpg'};
+                {'.pr3'};
+                {'.dat';'.hdr'};
+                {'.fdf'};
+                {'.hdf'};
+                {'.dti'};
+                {'.xys';'.html'};
+                {'.mvd2'};
+                {'.acff'};
+                {'.wat'};
+                {'.wlz'};
+                {'.lms'};
+                {'.xml';'.tiff'};
+                {'.zvi'};
+                {'.czi'};
+                {'.lsm';'.mdb'}};
+            
+            names = {'GIF (Graphics Interchange Format)';
+                'JPEG';
+                'JPEG 2000';
+                'PNG (Portable Network Graphics)';
+                'Text';
+                'TIFF (Tagged Image File Format)';
+                'Windows Bitmap';
+                
+                '3i SlideBook';
+                'Andor Bio-Imaging Division (ABD) TIFF';
+                'AIM';
+                'Alicona 3D';
+                'Amersham Biosciences Gel';
+                'Amira Mesh';
+                'Amnis FlowSight';
+                'Analyze 7.5';
+                'Animated PNG';
+                'Aperio AFI';
+                'Aperio SVS TIFF';
+                'Applied Precision CellWorX';
+                'AVI (Audio Video Interleave)';
+                'Axon Raw Format';
+                'BD Pathway';
+                'Becker & Hickl SPCImage';
+                'Bio-Rad Gel';
+                'Bio-Rad PIC';
+                'Bio-Rad SCN';
+                'Bitplane Imaris';
+                'Bruker MRI';
+                'Burleigh';
+                'Canon DNG';
+                'CellH5';
+                'Cellomics';
+                'cellSens VSI';
+                'CellVoyager';
+                'DeltaVision';
+                'DICOM';
+                'ECAT7';
+                'EPS (Encapsulated PostScript)';
+                'Evotec/PerkinElmer Opera Flex';
+                'FEI';
+                'FEI TIFF';
+                'FITS (Flexible Image Transport System)';
+                'Gatan Digital Micrograph';
+                'Gatan Digital Micrograph 2';
+                'Hamamatsu Aquacosmos NAF';
+                'Hamamatsu HIS';
+                'Hamamatsu ndpi';
+                'Hamamatsu VMS';
+                'Hitachi S-4800';
+                'I2I';
+                'ICS (Image Cytometry Standard)';
+                'Imacon';
+                'ImagePro Sequence';
+                'ImagePro Workspace';
+                'IMAGIC';
+                'IMOD';
+                'Improvision Openlab LIFF';
+                'Improvision Openlab Raw';
+                'Improvision TIFF';
+                'Imspector OBF';
+                'InCell 1000/2000';
+                'InCell 3000';
+                'INR';
+                'Inveon';
+                'IPLab';
+                'IVision';
+                'JEOL';
+                'JPK';
+                'JPX';
+                'Khoros VIFF (Visualization Image File Format) Bitmap';
+                'Kodak BIP';
+                'Lambert Instruments FLIM';
+                'LaVision Imspector';
+                'Leica LCS LEI';
+                'Leica LAS AF LIF (Leica Image File Format)';
+                'Leica SCN';
+                'LEO';
+                'Li-Cor L2D';
+                'LIM (Laboratory Imaging/Nikon)';
+                'MetaMorph 7.5 TIFF';
+                'MetaMorph Stack (STK)';
+                'MIAS (Maia Scientific)';
+                'Micro-Manager';
+                'MINC MRI';
+                'Minolta MRW';
+                'MNG (Multiple-image Network Graphics)';
+                'Molecular Imaging';
+                'MRC (Medical Research Council)';
+                'NEF (Nikon Electronic Format)';
+                'NIfTI';
+                'Nikon Elements TIFF';
+                'Nikon EZ-C1 TIFF';
+                'Nikon NIS-Elements ND2';
+                'NRRD (Nearly Raw Raster Data)';
+                'Olympus CellR/APL';
+                'Olympus FluoView FV1000';
+                'Olympus FluoView TIFF';
+                'Olympus ScanR';
+                'Olympus SIS TIFF';
+                'OME-TIFF';
+                'OME-XML';
+                'Oxford Instruments';
+                'PCORAW';
+                'PCX (PC Paintbrush)';
+                'Perkin Elmer Densitometer';
+                'PerkinElmer Nuance';
+                'PerkinElmer Operetta';
+                'PerkinElmer UltraVIEW';
+                'Portable Any Map';
+                'Adobe Photoshop PSD';
+                'Photoshop TIFF';
+                'PicoQuant Bin';
+                'PICT (Macintosh Picture)';
+                'Prairie Technologies TIFF';
+                'Quesant';
+                'QuickTime Movie';
+                'RHK';
+                'SBIG';
+                'Seiko';
+                'SimplePCI & HCImage';
+                'SimplePCI & HCImage TIFF';
+                'SM Camera';
+                'SPIDER';
+                'Targa';
+                'TillPhotonics TillVision';
+                'Topometrix';
+                'Trestle';
+                'UBM';
+                'Unisoku';
+                'Varian FDF';
+                'Veeco AFM';
+                'VG SAM';
+                'VisiTech XYS';
+                'Volocity';
+                'Volocity Library Clipping';
+                'WA-TOP';
+                'Woolz';
+                'Zeiss Axio CSM';
+                'Zeiss AxioVision TIFF';
+                'Zeiss AxioVision ZVI (Zeiss Vision Image)';
+                'Zeiss CZI';
+                'Zeiss LSM (Laser Scanning Microscope) 510/710'};
+
+	formats = [names extensions];
+end
diff --git a/src/MATLAB/+MicroscopeData/+Original/ReadImages.m b/src/MATLAB/+MicroscopeData/+Original/ReadImages.m
new file mode 100644
index 0000000000000000000000000000000000000000..70732a9ceafa2e4164e09e56fce0c206bc236db1
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/ReadImages.m
@@ -0,0 +1,37 @@
+function [ seriesImages ] = ReadImages( dirIn, fileNameIn, seriesNum )
+%READIMAGES [seriesImages] = ReadImages(dirIn, fileNameIn)
+%This function will read in data generated by a microscope and return the
+%image data along with the metadata associated with each of the images.
+%   dirIn and fileNameIn are both optional arguments where if either are
+%   empty, a get file dialog will appear.
+%
+%   Microscope data may contain more than one set of images thus the output
+%   of this function will each be cells. Each cell will contain the image
+%   data for each microscope run.  There are more than one "series" typically
+%   when there are multiple stage positions that are being captured over an
+%   experiment.
+
+%% get file and properties
+if (~exist('dirIn','var') || ~exist('fileNameIn','var') || isempty(dirIn) || isempty(fileNameIn))
+    [fileNameIn,dirIn,~] = uigetfile('*.*','Select Microscope Data');
+    if (fileNameIn==0)
+        warning('No images read');
+        return
+    end
+end
+
+if (~exist('seriesNum','var'))
+    seriesNum = [];
+end
+
+[datasetPath,datasetName,datasetExt] = fileparts(fullfile(dirIn,fileNameIn));
+
+MicroscopeData.Original.BioFormats.CheckJarPath();
+
+bfReader = MicroscopeData.Original.BioFormats.GetReader(fullfile(datasetPath,[datasetName,datasetExt]));
+
+seriesImages = MicroscopeData.Original.BioFormats.GetImages(bfReader,seriesNum);
+
+bfReader.close();
+end
+
diff --git a/src/MATLAB/+MicroscopeData/+Original/ReadMetadata.m b/src/MATLAB/+MicroscopeData/+Original/ReadMetadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..bebfdb2e1b8aeb0f05acef01b3975c05b26036ce
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/+Original/ReadMetadata.m
@@ -0,0 +1,46 @@
+function [ seriesMetaData, varargout ] = ReadMetadata( dirIn, fileNameIn )
+%READMETADATA [seriesMetadata] = ReadData(dirIn, fileNameIn)
+%This function will read in data generated by a microscope and return the
+%image data along with the metadata associated with each of the images.
+%   dirIn and fileNameIn are both optional arguments where if either are
+%   empty, a get file dialog will appear.
+%
+%   Microscope data may contain more than one set of images thus the output
+%   of this function will each be cells. Each cell will contain the associated
+%   metadata for each microscope run.  There are more than one "series"
+%   typically when there are multiple stage positions that are being captured
+%   over an experiment.
+
+%% get file and properties
+if (~exist('dirIn','var') || ~exist('fileNameIn','var') || isempty(dirIn) || isempty(fileNameIn))
+    [fileNameIn,dirIn,~] = uigetfile('*.*','Select Microscope Data');
+    if (fileNameIn==0)
+        warning('No images read');
+        return
+    end
+end
+
+[datasetPath,datasetName,datasetExt] = fileparts(fullfile(dirIn,fileNameIn));
+
+MicroscopeData.Original.BioFormats.CheckJarPath();
+
+bfReader = MicroscopeData.Original.BioFormats.GetReader(fullfile(datasetPath,[datasetName,datasetExt]));
+
+if (~isempty(bfReader))
+    [seriesMetaData, omeMetaData, orgMetaData] = MicroscopeData.Original.BioFormats.GetMetadata(bfReader,datasetExt);
+    bfReader.close();
+else
+    seriesMetaData = [];
+    omeMetaData = [];
+    orgMetaData = [];
+end
+
+if (nargout>1)
+    varargout{1} = omeMetaData;
+end
+if (nargout>2)
+    varargout{2} = orgMetaData;
+end
+
+end
+
diff --git a/src/MATLAB/+MicroscopeData/CreateMetadata.m b/src/MATLAB/+MicroscopeData/CreateMetadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..d163e93a166e0052a4085b7a0b95734930d9c240
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/CreateMetadata.m
@@ -0,0 +1,43 @@
+function CreateMetadata(root,imageData,quiet)
+
+if (isempty(root))
+    if (isfield(imageData,'imageDir'))
+        openDir = imageData.imageDir;
+    else
+        openDir = [];
+    end
+    root = uigetdir(openDir,'Directory to Place Metadata');
+    if (root==0)
+        return
+    end
+end
+
+if (~exist(root,'dir'))
+    mkdir(root);
+end
+
+if (~exist('quiet','var') || isempty(quiet))
+    quiet = 0;
+end
+
+fileName = fullfile(root,[imageData.DatasetName '.json']);
+
+if (~quiet)
+    fprintf('Creating Metadata %s...',fileName);
+end
+
+if (isfield(imageData,'imageDir'))
+    imageData = rmfield(imageData,'imageDir');
+end
+
+jsonMetadata = Utils.CreateJSON(imageData);
+fileHandle = fopen(fileName,'wt');
+
+fwrite(fileHandle, jsonMetadata, 'char');
+
+fclose(fileHandle);
+
+if (~quiet)
+    fprintf('Done\n');
+end
+end
\ No newline at end of file
diff --git a/src/MATLAB/+MicroscopeData/GetEmptyMetadata.m b/src/MATLAB/+MicroscopeData/GetEmptyMetadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..5431d4e2aa83307b2f50041d123431f479bbe18c
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/GetEmptyMetadata.m
@@ -0,0 +1,21 @@
+function imageData = GetEmptyMetadata()
+imageData = struct('DatasetName',{''},...
+    'Dimensions',{[0,0,0]},...
+    'NumberOfChannels',{0},...
+    'NumberOfFrames',{0},...
+    'PixelPhysicalSize',{[1,1,1]},...
+    'ChannelNames',{''},...
+    'StartCaptureDate',{},...
+    'ChannelColors',{[]},...
+    'PixelFormat',{''});
+
+imageData(1).DatasetName = '';
+imageData.Dimensions = [0,0,0];
+imageData.NumberOfChannels = 0;
+imageData.NumberOfFrames = 0;
+imageData.PixelPhysicalSize = [0,0,0];
+imageData.ChannelNames = '';
+imageData.StartCaptureDate = '';
+imageData.ChannelColors = [];
+imageData.PixelFormat = 'none';
+end
\ No newline at end of file
diff --git a/src/MATLAB/+MicroscopeData/MakeMetadataFromFolder.m b/src/MATLAB/+MicroscopeData/MakeMetadataFromFolder.m
new file mode 100644
index 0000000000000000000000000000000000000000..5bff00d2817e5fb6e9fd82e664ca101adfa98999
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/MakeMetadataFromFolder.m
@@ -0,0 +1,42 @@
+function imageData = MakeMetadataFromFolder(rootDir,datasetName)
+    imageData = MicroscopeData.GetEmptyMetadata();
+    
+    tifList = dir(fullfile(rootDir,[datasetName '*.tif']));
+    nameList = {tifList.name}.';
+    
+    prefixStr = regexptranslate('escape',datasetName);
+    
+    tokMatch = regexp(nameList, [prefixStr '_c(\d+)_t(\d+)_z(\d+)\.tif'], 'tokens','once');
+    
+    bValidTok = cellfun(@(x)(length(x)==3), tokMatch);
+    chkTok = vertcat(tokMatch{bValidTok});
+    
+    ctzVals = cellfun(@(x)(str2double(x)), chkTok);
+    
+    ctzMax = max(ctzVals,[],1);
+    
+    chkFilename = sprintf('%s_c%02d_t%04d_z%04d.tif', datasetName,1,1,1);
+    imInfo = imfinfo(fullfile(rootDir,chkFilename));
+    
+    imageData.DatasetName = datasetName;
+    imageData.Dimensions = [imInfo.Width, imInfo.Height, ctzMax(3)];
+    imageData.NumberOfChannels = ctzMax(1);
+    imageData.NumberOfFrames = ctzMax(2);
+    imageData.PixelPhysicalSize = [1,1,1];
+    
+    colors = [1,0,0;...
+          0,1,0;...
+          0,0,1;...
+          0,1,1;...
+          1,0,1;...
+          1,1,0];
+    
+    for c=1:imageData.NumberOfChannels
+        imageData.ChannelNames = [imageData.ChannelNames; {sprintf('Channel %d',c)}];
+        colidx = mod(c,size(colors,1));
+        imageData.ChannelColors = vertcat(imageData.ChannelColors,colors(colidx,:));
+    end
+
+    imageData.StartCaptureDate = datestr(now,'yyyy-mm-dd HH:MM:SS');
+    imageData.PixelFormat = MicroscopeData.Helper.GetPixelTypeTIF(fullfile(rootDir,chkFilename));
+end
diff --git a/src/MATLAB/+MicroscopeData/ReadMetadata.m b/src/MATLAB/+MicroscopeData/ReadMetadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..708499193262c55613445264ce5c89a3e23bfdfa
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/ReadMetadata.m
@@ -0,0 +1,46 @@
+function [imageData,jsonDir,jsonFile] = ReadMetadata(root,forcePrompt,promptTitle)
+
+imageData = [];
+jsonDir = [];
+jsonFile = [];
+
+if ( ~exist('promptTitle','var') )
+    promptTitle = [];
+end
+
+if (~exist('forcePrompt','var'))
+    forcePrompt = [];
+end
+
+if (~exist('root','var') || isempty(root))
+    root = '';
+end
+
+if (~isempty(root) && ~any(strcmp(root(end),{'\','/'})) && exist(root,'dir'))
+    root = fullfile(root, filesep);
+end
+
+% This is to help when the filename might have '.' whithin them
+% TODO rework this logic %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+if (exist([root,'.json'],'file'))
+    root = [root,'.json'];
+end
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+bAlwaysPrompt = (~isempty(forcePrompt) && forcePrompt);
+if ( ~bAlwaysPrompt )
+    [imageData,jsonDir,jsonFile] = MicroscopeData.ReadMetadataFile(root);
+end
+
+bNeedData = (isempty(forcePrompt) && isempty(imageData));
+if ( bAlwaysPrompt || bNeedData )
+    [fileName,rootDir,filterIndex] = uigetfile({'*.json','Metadata files (*.json)';'*.*','All Files (*.*)'},promptTitle,root);
+    if (filterIndex==0)
+        return
+    end
+
+    root = fullfile(rootDir,fileName);
+    [imageData,jsonDir,jsonFile] = MicroscopeData.ReadMetadataFile(root);
+end
+
+end
diff --git a/src/MATLAB/+MicroscopeData/ReadMetadataFile.m b/src/MATLAB/+MicroscopeData/ReadMetadataFile.m
new file mode 100644
index 0000000000000000000000000000000000000000..19c3b5fc2358b368d6b5f668fc683fa3de098479
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/ReadMetadataFile.m
@@ -0,0 +1,64 @@
+function [imageData,jsonDir,jsonFile] = ReadMetadataFile(metadataPath)
+    imageData = [];
+    jsonDir = [];
+    jsonFile = [];
+    
+    if ( isempty(metadataPath) )
+        metadataPath = '';
+    end
+    
+    chkPath = findValidJSON(metadataPath);
+    if ( isempty(chkPath) )
+        return;
+    end
+
+    fileHandle = fopen(chkPath);
+
+    jsonData = fread(fileHandle,'*char').';
+    imageData = Utils.ParseJSON(jsonData);
+
+    fclose(fileHandle);
+
+    [rootDir,fileName] = fileparts(chkPath);
+    
+    jsonDir = rootDir;
+    jsonFile = [fileName '.json'];
+
+    imageData.imageDir = jsonDir;
+end
+
+function jsonPath = findValidJSON(chkPath)
+    jsonPath = [];
+    
+    [rootDir,fileName,ext] = fileparts(chkPath);
+    if ( ~isempty(ext) )
+        if ( ~strcmpi(ext,'.json') )
+            return;
+        end
+        chkPath = fullfile(rootDir,[fileName,'.json']);
+    elseif (~isempty(fileName))
+        % case root has a file name
+        if (~exist(fullfile(rootDir,[fileName,'.json']),'file'))
+            return;
+        end
+        chkPath = fullfile(rootDir,[fileName,'.json']);
+    elseif (~isempty(rootDir))
+        % case root is a path (e.g. \ terminated)
+        jsonList = dir(fullfile(rootDir,'*.json'));
+        if (isempty(jsonList))
+            return;
+        end
+        chkPath = fullfile(rootDir,jsonList(1).name);
+    end
+    
+    [~,~,ext] = fileparts(chkPath);
+    if ( ~strcmpi(ext,'.json') )
+        return;
+    end
+
+    if (~exist(chkPath,'file'))
+        return
+    end
+    
+    jsonPath = chkPath;
+end
diff --git a/src/MATLAB/+MicroscopeData/Reader.m b/src/MATLAB/+MicroscopeData/Reader.m
new file mode 100644
index 0000000000000000000000000000000000000000..abafe06ba52e612c88a0e13efd75e934c17b3476
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/Reader.m
@@ -0,0 +1,62 @@
+% [IM, IMAGEDATA] = MicroscopeData.Reader([path], varargin)
+%
+% Optional Parameters (Key,Value pairs):
+%
+% imageData - Input metadata, if specified, the optional path argument is ignored
+% chanList - List of channels to read
+% timeRange - Range min and max times to read
+% roi_xyz - x,y,z min and max roi to read
+% outType - Desired output type, conversion is applied if different from image
+% normalize - Normalize images on [0,1] per frame before conersion to output type
+% verbose - Display verbose output and timing information
+% prompt - False to completely disable prompts, true to force prompt, leave unspecified or empty for default prompt behavior
+% promptTitle - Open dialog title in the case that prompting is required
+
+function [im, imD] = Reader(varargin)
+im = [];
+imD = [];
+
+dataTypeLookup = {'uint8';'uint16';'uint32';'uint64';
+                  'int8';'int16';'int32';'int64';
+                  'single';'double';
+                  'logical'};
+
+args = MicroscopeData.Helper.ParseReaderInputs(varargin{:});
+
+loadPath = '';
+if ( ~isempty(args.imageData) )
+    loadPath = args.imageData.imageDir;
+elseif ( ~isempty(args.path) )
+    loadPath = args.path;
+end
+
+if ( args.prompt )
+    imD = MicroscopeData.ReadMetadata(loadPath,args.prompt,args.promptTitle);
+elseif ( isempty(args.imageData) )
+    imD = MicroscopeData.ReadMetadata(loadPath,args.prompt,args.promptTitle);
+else
+    imD = args.imageData;
+end
+
+if (isempty(imD))
+    warning('No image read!');
+    return
+end
+
+imPath = imD.imageDir;
+hdf5File = fullfile(imPath,[imD.DatasetName '.h5']);
+if ( exist(hdf5File,'file') )
+    [im,imD] = MicroscopeData.ReaderH5('imageData',imD, 'chanList',args.chanList, 'timeRange',args.timeRange, 'roi_xyz',args.roi_xyz,...
+                                        'outType',args.outType, 'normalize',args.normalize, 'verbose',args.verbose, 'prompt',false);
+	return;
+end
+
+tifFile = fullfile(imPath,sprintf('%s_c%02d_t%04d_z%04d.tif',imD.DatasetName,1,1,1));
+if ( exist(tifFile,'file') )
+    [im,imD] = MicroscopeData.ReaderTIF('imageData',imD, 'chanList',args.chanList, 'timeRange',args.timeRange, 'roi_xyz',args.roi_xyz,...
+                                        'outType',args.outType, 'normalize',args.normalize, 'verbose',args.verbose, 'prompt',false);
+    return;
+end
+
+warning('No supported image type found!');
+end
diff --git a/src/MATLAB/+MicroscopeData/ReaderH5.m b/src/MATLAB/+MicroscopeData/ReaderH5.m
new file mode 100644
index 0000000000000000000000000000000000000000..54a6227016c043339967af3c17cbae517867db57
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/ReaderH5.m
@@ -0,0 +1,141 @@
+% [IM, IMAGEDATA] = MicroscopeData.Sandbox.ReaderH5([path], varargin)
+%
+% Optional Parameters (Key,Value pairs):
+%
+% imageData - Input metadata, if specified, the optional path argument is ignored
+% chanList - List of channels to read
+% timeRange - Range min and max times to read
+% roi_xyz - x,y,z min and max roi to read
+% outType - Desired output type, conversion is applied if different from image
+% normalize - Normalize images on [0,1] per frame before conersion to output type
+% verbose - Display verbose output and timing information
+% prompt - False to completely disable prompts, true to force prompt, leave unspecified or empty for default prompt behavior
+% promptTitle - Open dialog title in the case that prompting is required
+
+function [im, imD] = ReaderH5(varargin)
+im = [];
+
+dataTypeLookup = {'uint8';'uint16';'uint32';'uint64';
+                  'int8';'int16';'int32';'int64';
+                  'single';'double';
+                  'logical'};
+
+dataTypeSize = [1;2;4;8;
+                1;2;4;8;
+                4;8;
+                1];
+
+args = MicroscopeData.Helper.ParseReaderInputs(varargin{:});
+
+loadPath = '';
+if ( ~isempty(args.imageData) )
+    loadPath = args.imageData.imageDir;
+elseif ( ~isempty(args.path) )
+    loadPath = args.path;
+end
+
+if ( args.prompt )
+    imD = MicroscopeData.ReadMetadata(loadPath,args.prompt,args.promptTitle);
+elseif ( isempty(args.imageData) )
+    imD = MicroscopeData.ReadMetadata(loadPath,args.prompt,args.promptTitle);
+else
+    imD = args.imageData;
+end
+
+imPath = imD.imageDir;
+
+if (isempty(args.chanList))
+    args.chanList = 1:imD.NumberOfChannels;
+end
+
+if (isempty(args.timeRange))
+    args.timeRange = [1 imD.NumberOfFrames];
+end
+
+if (isempty(args.roi_xyz))
+    args.roi_xyz = [1 1 1; imD.Dimensions];
+end
+
+if (~exist(fullfile(imPath,[imD.DatasetName '.h5']),'file'))
+    warning('No image to read!');
+    return
+end
+
+inType = class(h5read(fullfile(imPath,[imD.DatasetName '.h5']),'/Data',[1 1 1 1 1],[1 1 1 1 1]));
+inIdx = find(strcmp(inType,dataTypeLookup));
+if ( ~isempty(inIdx) )
+    inBytes = dataTypeSize(inIdx);
+else
+    error('Unsupported image type!');
+end
+
+if (~isfield(imD,'PixelFormat'))
+    imD.PixelFormat = inType;
+end
+
+if ( isempty(args.outType) )
+    if (strcmp(imD.PixelFormat,'logical'))
+        args.outType = 'logical';
+    else
+        args.outType = inType;
+    end
+elseif ( ~any(strcmp(args.outType,dataTypeLookup)) )
+    error('Unsupported output type!');
+end
+
+outIdx = find(strcmp(args.outType,dataTypeLookup));
+if ( ~isempty(outIdx) )
+    outBytes = dataTypeSize(outIdx);
+end
+
+convert = ~strcmpi(inType,args.outType) || args.normalize;
+imSize = [diff(Utils.SwapXY_RC(args.roi_xyz),1)+1,length(args.chanList),(args.timeRange(2)-args.timeRange(1)+1)];
+if (~strcmpi(args.outType,'logical'))
+    im = zeros(imSize, args.outType);
+else
+    im = false(imSize);
+end
+
+if ( args.verbose )
+    orgSize = [imD.Dimensions(1),imD.Dimensions(2),imD.Dimensions(3),imD.NumberOfChannels,imD.NumberOfFrames];
+    fprintf('Reading (%d,%d,%d,%d,%d) %s %5.2fMB --> Into (%d,%d,%d,%d,%d) %s %5.2fMB,',...
+        orgSize(2),orgSize(1),orgSize(3),orgSize(4),orgSize(5),inType,...
+        (prod(orgSize)*inBytes)/(1024*1024),...
+        imSize(1),imSize(2),imSize(3),imSize(4),imSize(5),args.outType,...
+        (prod(imSize)*outBytes)/(1024*1024));
+end
+
+tic
+if ( convert )
+    for c=1:length(args.chanList)
+        for t=1:imSize(5)
+            tempIm = h5read(fullfile(imPath,[imD.DatasetName '.h5']),'/Data', [Utils.SwapXY_RC(args.roi_xyz(1,:)) args.chanList(c) t+args.timeRange(1)-1], [imSize(1:3) 1 1]);
+            im(:,:,:,c,t) = ImUtils.ConvertType(tempIm,args.outType,args.normalize);
+        end
+    end
+
+    clear tempIm;
+else
+    for c=1:length(args.chanList)
+        im(:,:,:,c,:) = h5read(fullfile(imPath,[imD.DatasetName '.h5']),'/Data', [Utils.SwapXY_RC(args.roi_xyz(1,:)) args.chanList(c) args.timeRange(1)], [imSize(1:3) 1 imSize(5)]);
+    end
+end
+if (args.verbose)
+    fprintf(' took:%s\n',Utils.PrintTime(toc));
+end
+
+imD.Dimensions = Utils.SwapXY_RC(imSize(1:3));
+imD.NumberOfChannels = size(im,4);
+imD.NumberOfFrames = size(im,5);
+
+if (isfield(imD,'ChannelNames') && ~isempty(imD.ChannelNames))
+    imD.ChannelNames = imD.ChannelNames(args.chanList)';
+else
+    imD.ChannelNames = {};
+end
+if (isfield(imD,'ChannelColors') && ~isempty(imD.ChannelColors))
+    imD.ChannelColors = imD.ChannelColors(args.chanList,:);
+else
+    imD.ChannelColors = [];
+end
+end
diff --git a/src/MATLAB/+MicroscopeData/ReaderTIF.m b/src/MATLAB/+MicroscopeData/ReaderTIF.m
new file mode 100644
index 0000000000000000000000000000000000000000..8141f486e07bb4d1ab1f7c40d20f400e3c6aa3fc
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/ReaderTIF.m
@@ -0,0 +1,175 @@
+% [IM, IMAGEDATA] = MicroscopeData.Reader([path], varargin)
+%
+% Optional Parameters (Key,Value pairs):
+%
+% imageData - Input metadata, if specified, the optional path argument is ignored
+% chanList - List of channels to read
+% timeRange - Range min and max times to read
+% roi_xyz - x,y,z min and max roi to read
+% outType - Desired output type, conversion is applied if different from image
+% normalize - Normalize images on [0,1] per frame before conersion to output type
+% verbose - Display verbose output and timing information
+% prompt - False to completely disable prompts, true to force prompt, leave unspecified or empty for default prompt behavior
+% promptTitle - Open dialog title in the case that prompting is required
+
+function [im, imD] = ReaderTIF(varargin)
+im = [];
+
+dataTypeLookup = {'uint8';'uint16';'uint32';'uint64';
+                  'int8';'int16';'int32';'int64';
+                  'single';'double';
+                  'logical'};
+
+dataTypeSize = [1;2;4;8;
+                1;2;4;8;
+                4;8;
+                1];
+
+args = MicroscopeData.Helper.ParseReaderInputs(varargin{:});
+
+loadPath = '';
+if ( ~isempty(args.imageData) )
+    loadPath = args.imageData.imageDir;
+elseif ( ~isempty(args.path) )
+    loadPath = args.path;
+end
+
+if ( args.prompt )
+    imD = MicroscopeData.ReadMetadata(loadPath,args.prompt,args.promptTitle);
+elseif ( isempty(args.imageData) )
+    imD = MicroscopeData.ReadMetadata(loadPath,args.prompt,args.promptTitle);
+else
+    imD = args.imageData;
+end
+
+if (isempty(imD))
+    warning('No image read!');
+    return
+end
+
+imPath = imD.imageDir;
+
+if (isempty(args.chanList))
+    args.chanList = 1:imD.NumberOfChannels;
+end
+
+if (isempty(args.timeRange))
+    args.timeRange = [1 imD.NumberOfFrames];
+end
+
+if (isempty(args.roi_xyz))
+    args.roi_xyz = [1 1 1; imD.Dimensions];
+end
+
+if (isempty(args.normalize))
+    args.normalize = false;
+end
+
+if (~exist(fullfile(imPath,sprintf('%s_c%02d_t%04d_z%04d.tif',imD.DatasetName,1,1,1)),'file'))
+    warning('No image to read!');
+    return
+end
+
+useROI = (nnz(args.roi_xyz(:,1:2) ~= [1 1;imD.Dimensions(1:2)]) > 0);
+
+chkFilename = fullfile(imPath,sprintf('%s_c%02d_t%04d_z%04d.tif',imD.DatasetName,1,1,1));
+inType = MicroscopeData.Helper.GetPixelTypeTIF(chkFilename);
+inIdx = find(strcmp(inType,dataTypeLookup));
+if ( ~isempty(inIdx) )
+    inBytes = dataTypeSize(inIdx);
+else
+    error('Unsupported image type!');
+end
+
+if (~isfield(imD,'PixelFormat'))
+    imD.PixelFormat = inType;
+end
+
+if ( isempty(args.outType) )
+    if (strcmp(imD.PixelFormat,'logical'))
+        args.outType = 'logical';
+    else
+        args.outType = inType;
+    end
+elseif ( ~any(strcmp(args.outType,dataTypeLookup)) )
+    error('Unsupported output type!');
+end
+
+outIdx = find(strcmp(args.outType,dataTypeLookup));
+if ( ~isempty(outIdx) )
+    outBytes = dataTypeSize(outIdx);
+end
+
+convert = ~strcmpi(inType,args.outType) || args.normalize;
+imSize = [diff(Utils.SwapXY_RC(args.roi_xyz),1)+1,length(args.chanList),(args.timeRange(2)-args.timeRange(1)+1)];
+if (~strcmpi(args.outType,'logical'))
+    im = zeros(imSize, args.outType);
+else
+    im = false(imSize);
+end
+
+if ( args.verbose )
+    orgSize = [imD.Dimensions(1),imD.Dimensions(2),imD.Dimensions(3),imD.NumberOfChannels,imD.NumberOfFrames];
+    fprintf('Reading (%d,%d,%d,%d,%d) %s %5.2fMB --> Into (%d,%d,%d,%d,%d) %s %5.2fMB,',...
+        orgSize(1),orgSize(2),orgSize(3),orgSize(4),orgSize(5),inType,...
+        (prod(orgSize)*inBytes)/(1024*1024),...
+        imSize(1),imSize(2),imSize(3),imSize(4),imSize(5),args.outType,...
+        (prod(imSize)*outBytes)/(1024*1024));
+end
+
+if ( args.verbose )
+    iter = imSize(5)*length(args.chanList)*imSize(3);
+    cp = Utils.CmdlnProgress(iter,true,sprintf('Reading %s...',imD.DatasetName));
+    i=1;
+end
+
+tic
+for t=1:imSize(5)
+    timeVal = t+args.timeRange(1)-1;
+    for c=1:length(args.chanList)
+        for z=1:imSize(3)
+            zVal = z+args.roi_xyz(1,3)-1;
+
+            tifName = fullfile(imPath,sprintf('%s_c%02d_t%04d_z%04d.tif',imD.DatasetName,args.chanList(c),timeVal,zVal));
+            if (convert || useROI)
+                tempIm(:,:,z) = imread(tifName,'TIF');
+            else
+                im(:,:,z,c,t) = imread(tifName,'TIF');
+            end
+
+            if ( args.verbose )
+                cp.PrintProgress(i);
+                i = i+1;
+            end
+        end
+
+        if (convert)
+            im(:,:,:,c,t) = ImUtils.ConvertType(...
+                tempIm(args.roi_xyz(1,2):args.roi_xyz(2,2),args.roi_xyz(1,1):args.roi_xyz(2,1),:),...
+                args.outType,args.normalize);
+        elseif (useROI)
+            im(:,:,:,c,t) = tempIm(args.roi_xyz(1,2):args.roi_xyz(2,2),args.roi_xyz(1,1):args.roi_xyz(2,1),:);
+        end
+    end
+end
+
+if ( args.verbose )
+    cp.ClearProgress(true);
+    fprintf(' took:%s\n',Utils.PrintTime(toc));
+end
+
+imD.Dimensions = Utils.SwapXY_RC(imSize(1:3));
+imD.NumberOfChannels = size(im,4);
+imD.NumberOfFrames = size(im,5);
+
+if (isfield(imD,'ChannelNames') && ~isempty(imD.ChannelNames))
+    imD.ChannelNames = imD.ChannelNames(args.chanList)';
+else
+    imD.ChannelNames = {};
+end
+if (isfield(imD,'ChannelColors') && ~isempty(imD.ChannelColors))
+    imD.ChannelColors = imD.ChannelColors(args.chanList,:);
+else
+    imD.ChannelColors = [];
+end
+end
diff --git a/src/MATLAB/+MicroscopeData/Writer.m b/src/MATLAB/+MicroscopeData/Writer.m
new file mode 100644
index 0000000000000000000000000000000000000000..a92373d112b18ce3f5cf365aec09100039c6a08f
--- /dev/null
+++ b/src/MATLAB/+MicroscopeData/Writer.m
@@ -0,0 +1,192 @@
+% TIFFWRITER(IM, PREFIX, IMAGEDATA, TIMELIST, CHANLIST, ZLIST, QUIET)
+% TIMELIST, CHANLIST, and ZLIST are optional; pass in empty [] for the
+% arguments that come prior to the one you would like to populate.
+%
+% IM = the image data to write. Assumes a 5-D image in the format
+% (X,Y,Z,channel,time). If the image already exists and TIMELIST, CHANLIST,
+% and ZLIST are populated with less then the whole image, the existing
+% image is updated.  If the file does not exist and the image data doesn't
+% fill the whole image, the rest will be filled in with black (zeros)
+% frames.
+%
+% PREFIX = filepath in the format ('c:\path\FilePrefix') unless there is no
+% imagedata in which case it should be ('c:\path)
+% IMAGEDATA = metadata that will be written to accompany the image.  If you
+% want this generated from the image data only, this paramater should be
+% just a string representing the dataset name.  See PREFIX above in such
+% case.
+% TIMELIST = a list of frames that the fifth dimention represents
+% CHANLIST = the channels that the input image represents
+% ZLIST = the z slices that the input image represents
+% QUITE = suppress printing out progress
+
+function Writer(im, outDir, imageData, timeList, chanList, zList, quiet)
+if (exist('tifflib') ~= 3)
+    tifflibLocation = which('/private/tifflib');
+    if (isempty(tifflibLocation))
+        error('tifflib does not exits on this machine!');
+    end
+    copyfile(tifflibLocation,'.');
+end
+
+if (~exist('quiet','var') || isempty(quiet))
+    quiet = 0;
+end
+
+if (exist('imageData','var') && ~isempty(imageData) && isfield(imageData,'DatasetName'))
+    idx = strfind(imageData.DatasetName,'"');
+    imageData.DatasetName(idx) = [];
+else
+    if isstruct(imageData)
+        error('ImageData struct is malformed!');
+    end
+    dName = imageData;
+    imageData = [];
+    imageData.DatasetName = dName;
+
+    imageData.Dimensions = Utils.SwapXY_RC(size(im));
+    imageData.NumberOfChannels = size(im,4);
+    imageData.NumberOfFrames = size(im,5);
+
+    imageData.PixelPhysicalSize = [1.0, 1.0, 1.0]; 
+end
+
+w = whos('im');
+switch w.class
+    case 'uint8'
+        tags.SampleFormat = Tiff.SampleFormat.UInt;
+        tags.BitsPerSample = 8;
+    case 'uint16'
+        tags.SampleFormat = Tiff.SampleFormat.UInt;
+        tags.BitsPerSample = 16;
+    case 'uint32'
+        tags.SampleFormat = Tiff.SampleFormat.UInt;
+        tags.BitsPerSample = 32;
+    case 'int8'
+        tags.SampleFormat = Tiff.SampleFormat.Int;
+        tags.BitsPerSample = 8;
+    case 'int16'
+        tags.SampleFormat = Tiff.SampleFormat.Int;
+        tags.BitsPerSample = 16;
+    case 'int32'
+        tags.SampleFormat = Tiff.SampleFormat.Int;
+        tags.BitsPerSample = 32;
+    case 'single'
+        tags.SampleFormat = Tiff.SampleFormat.IEEEFP;
+        tags.BitsPerSample = 32;
+    case 'double'
+        tags.SampleFormat = Tiff.SampleFormat.IEEEFP;
+        tags.BitsPerSample = 64;
+    case 'logical'
+        imtemp = zeros(size(im),'uint8');
+        imtemp(im) = 255;
+        im = imtemp;
+        clear imtemp
+        tags.SampleFormat = Tiff.SampleFormat.UInt;
+        tags.BitsPerSample = 8;
+    otherwise
+        error('Image type unsupported!');
+end
+
+if (~isfield(imageData,'PixelFormat'))
+    imageData.PixelFormat = w.class;
+end
+
+if (exist('outDir','var') && ~isempty(outDir))
+    idx = strfind(outDir,'"');
+    outDir(idx) = [];
+elseif (isfield(imageData,'imageDir') && ~isempty(imageData.imageDir))
+    outDir = imageData.imageDir;
+else
+    outDir = fullfile('.',imageData.DatasetName);
+end
+
+MicroscopeData.CreateMetadata(outDir,imageData,quiet);
+
+if (~exist('timeList','var') || isempty(timeList))
+    timeList = 1:imageData.NumberOfFrames;
+else
+    if (max(timeList(:))>imageData.NumberOfFrames)
+        error('A value in timeList is greater than the number of frames in the image data!');
+    end
+end
+if (size(im,5)~=length(timeList))
+    error('There are %d frames and %d frames to be written!',size(im,5),length(timeList));
+end
+
+if (~exist('chanList','var') || isempty(chanList))
+    chanList = 1:imageData.NumberOfChannels;
+else
+    if (max(chanList(:))>imageData.NumberOfChannels)
+        error('A value in chanList is greater than the number of channels in the image data!');
+    end
+end
+if (size(im,4)~=length(chanList))
+    error('There are %d channels and %d channels to be written!',size(im,4),length(chanList));
+end
+
+if (~exist('zList','var') || isempty(zList))
+    zList = 1:imageData.Dimensions(3);
+else
+    if (max(zList(:))>imageData.Dimensions(3))
+        error('A value in zList is greater than the z dimension in the image data!');
+    end
+end
+if (size(im,3)~=length(zList))
+    error('There are %d z images and %d z images to be written!',size(im,3),length(zList));
+end
+
+tags.ImageLength = size(im,1);
+tags.ImageWidth = size(im,2);
+tags.RowsPerStrip = size(im,2);
+tags.Photometric = Tiff.Photometric.MinIsBlack;
+tags.ExtraSamples = Tiff.ExtraSamples.Unspecified;
+tags.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky;
+tags.SamplesPerPixel = 1;
+tags.Compression = Tiff.Compression.LZW;
+tags.Software = 'MATLAB';
+
+if (~quiet)
+    iter = length(timeList)*length(chanList)*length(zList);
+    cp = Utils.CmdlnProgress(iter,true,sprintf('Writing %s...',imageData.DatasetName));
+    i=1;
+end
+
+isBig = false;
+if (tags.BitsPerSample/8 * prod(imageData.Dimensions) > 0.95*2^32)
+    isBig = true;
+end
+
+tic
+for t=1:length(timeList)
+    for c=1:length(chanList)
+        for z=1:length(zList)
+            if (isBig)
+                tiffObj = Tiff(fullfile(outDir,[imageData.DatasetName,sprintf('_c%02d_t%04d_z%04d.tif',chanList(c),timeList(t),zList(z))]),'w8');
+            else
+                tiffObj = Tiff(fullfile(outDir,[imageData.DatasetName,sprintf('_c%02d_t%04d_z%04d.tif',chanList(c),timeList(t),zList(z))]),'w');
+            end
+            tiffObj.setTag(tags);
+            tiffObj.write(im(:,:,z,c,t),tags);
+            tiffObj.close();
+
+%             fname = fullfile(outDir,[imageData.DatasetName,sprintf('_c%02d_t%04d_z%04d.tif',chanList(c),timeList(t),zList(z))]);
+%             imwrite(im(:,:,z,c,t),fname,'Compression','lzw');
+            
+            if (~quiet)
+                cp.PrintProgress(i);
+                i = i+1;
+            end
+
+        end
+    end
+end
+
+if (~quiet)
+    cp.ClearProgress();
+    fprintf('Wrote %.0fMB in %s\n',...
+        ((tags.BitsPerSample/8)*prod(imageData.Dimensions)*imageData.NumberOfChannels*imageData.NumberOfFrames)/(1024*1024),...
+        Utils.PrintTime(toc));
+end
+end
+
diff --git a/src/MATLAB/+Utils/CmdlnProgress.m b/src/MATLAB/+Utils/CmdlnProgress.m
new file mode 100644
index 0000000000000000000000000000000000000000..320d8a5f59495a18b1b8d8038ec49b0ee39b4de3
--- /dev/null
+++ b/src/MATLAB/+Utils/CmdlnProgress.m
@@ -0,0 +1,130 @@
+classdef CmdlnProgress<handle
+    %PRINTPROGRESS prints the progress and the estimated time of completion on
+    %the commandline.
+    %ENSURE that the code that is being monitored does not have internal
+    %printing to the commandline.
+    % INIT - if init is true, then the val passed in will be used as the
+    % denominator when figuring out the percent completed.
+    % When init is false, the progress is deleted from the command line and the
+    % internal valuse reset.
+    % VAL - when updating the progress enter the number that will be used as
+    % the numerator for the percent completed and ensure that the second
+    % paramater is not used or empty.
+    %
+    % Usage -- Initalize by using CmdlnProgress(number of iterations, true, optionalTitle);
+    %          Update by using    PrintProgress(current iteration);
+    %          Clean up by using  PrintProgress(0,false);
+    
+    properties
+        backspaces
+        firstTime
+        total
+        useBs
+        titleText
+    end
+    
+    methods
+        function obj = CmdlnProgress(iterations,useBackspace,optionalTitle)
+            if(~exist('useBackspace', 'var') || isempty(useBackspace))
+                obj.useBs = true;
+            else
+                obj.useBs = useBackspace;
+            end
+            
+            if (~exist('optionalTitle','var') || isempty(optionalTitle))
+                obj.titleText = '';
+            else
+                obj.titleText = optionalTitle;
+            end
+            
+            obj.total = iterations;
+
+            obj.backspaces = [];
+            obj.firstTime = now;
+        end
+        
+        function SetMaxIterations(obj,iterations)
+            obj.total = iterations;
+        end
+        
+        function PrintProgress(obj,val)
+            cur = now;
+            
+            prcntDone = val / obj.total;
+            elpsTime = (cur - obj.firstTime) * 86400;
+            totalSec = elpsTime / prcntDone;
+            finDate = obj.firstTime + (totalSec / 86400);
+            timeLeft = (finDate - cur)*86400;
+            
+            if (~isempty(obj.titleText))
+                doneStr = sprintf('%s: %5.2f%%%% est. %s @ %s\n',...
+                    obj.titleText,...
+                    prcntDone*100,...
+                    Utils.PrintTime(timeLeft),...
+                    datestr(finDate,'HH:MM:SS dd-mmm-yy'));
+            else
+                doneStr = sprintf('%5.2f%%%% est. %s @ %s\n',...
+                    prcntDone*100,...
+                    Utils.PrintTime(timeLeft),...
+                    datestr(finDate,'HH:MM:SS dd-mmm-yy'));
+            end
+            fprintf([obj.backspaces,doneStr]);
+            
+            if(obj.useBs)
+                obj.backspaces = repmat(sprintf('\b'),1,length(doneStr)-1);
+            else
+                fprintf('\n');
+            end
+        end
+        
+        function ReprintProgress(obj,val)
+            cur = now;
+            
+            prcntDone = val / obj.total;
+            elpsTime = (cur - obj.firstTime) * 86400;
+            totalSec = elpsTime / prcntDone;
+            finDate = obj.firstTime + (totalSec / 86400);
+            timeLeft = (finDate - cur)*86400;
+            
+            if (~isempty(obj.titleText))
+                doneStr = sprintf('%s: %5.2f%%%% est. %s @ %s\n',...
+                    obj.titleText,...
+                    prcntDone*100,...
+                    Utils.PrintTime(timeLeft),...
+                    datestr(finDate,'HH:MM:SS dd-mmm-yy'));
+            else
+                doneStr = sprintf('%5.2f%%%% est. %s @ %s\n',...
+                    prcntDone*100,...
+                    Utils.PrintTime(timeLeft),...
+                    datestr(finDate,'HH:MM:SS dd-mmm-yy'));
+            end
+            
+            fprintf(doneStr);
+            
+            if(obj.useBs)
+                obj.backspaces = repmat(sprintf('\b'),1,length(doneStr)-1);
+            else
+                fprintf('\n');
+            end
+        end
+        
+        function ClearProgress(obj,printTotal)
+            if (~isempty(obj.backspaces))
+                fprintf(obj.backspaces);
+            end
+            if (exist('printTotal','var') && ~isempty(printTotal) && printTotal)
+                cur = now;
+                elpsTime = (cur - obj.firstTime) * 86400;
+                if (~isempty(obj.titleText))
+                    fprintf('%s took: %s\n',obj.titleText,Utils.PrintTime(elpsTime))
+                else
+                    fprintf('Took: %s\n',Utils.PrintTime(elpsTime))
+                end
+            end
+            obj.backspaces = [];
+            obj.firstTime = 0;
+            obj.total = 0;
+            obj.useBs = false;
+        end
+    end
+end
diff --git a/src/MATLAB/+Utils/CoordToInd.m b/src/MATLAB/+Utils/CoordToInd.m
new file mode 100644
index 0000000000000000000000000000000000000000..0a5ae3e55574b389e66c73e2ee1a2b5036d6387c
--- /dev/null
+++ b/src/MATLAB/+Utils/CoordToInd.m
@@ -0,0 +1,7 @@
+% CoordToInd - Convert subscript indices to linear array indices.
+% 
+% arrayIdx = CoordToInd(arraySize, coords)
+function arrayIdx = CoordToInd(arraySize, coords)
+    linSize = [1 cumprod(arraySize(1:end-1))];
+    arrayIdx = sum((coords-1) .* repmat(linSize, size(coords,1),1), 2) + 1;
+end
diff --git a/src/MATLAB/+Utils/CreateJSON.m b/src/MATLAB/+Utils/CreateJSON.m
new file mode 100644
index 0000000000000000000000000000000000000000..6d9d970d990a85353036fb8f11955bced226e586
--- /dev/null
+++ b/src/MATLAB/+Utils/CreateJSON.m
@@ -0,0 +1,155 @@
+function json = CreateJSON(data,bWhitespace)
+    if ( ~exist('bWhitespace','var') )
+        bWhitespace = true;
+    end
+    
+    spaceStruct = struct('line',{''}, 'space',{''}, 'indent',{''});
+    if ( bWhitespace )
+        spaceStruct = struct('line',{'\n'}, 'space',{' '}, 'indent',{'  '});
+    end
+    
+    if ( isstruct(data) )
+        json = writeObject(data,'', spaceStruct);
+    else
+        json = writeArray(data,'', spaceStruct);
+    end
+end
+
+function json = writeObject(data, spacePrefix, spaceStruct)
+    fields = fieldnames(data);
+    
+    fieldSep = ',';
+    objJSON = '';
+    
+    fieldPattern = ['%s"%s"' spaceStruct.space ':' spaceStruct.space '%s%s'];
+    patternPad = length(sprintf(fieldPattern,'','','',''));
+    
+    fieldPrefix = [spacePrefix spaceStruct.indent];
+    for i=1:length(fields)
+        if ( i == length(fields) )
+            fieldSep = '';
+        end
+        
+        elemPad = repmat(spaceStruct.space, 1,patternPad+length(fields{i}));
+        elemPrefix = [fieldPrefix elemPad];
+        
+        valJSON = writeValue(data.(fields{i}), elemPrefix, spaceStruct);
+        fieldJSON = sprintf([spaceStruct.line fieldPattern], fieldPrefix, fields{i}, valJSON,fieldSep);
+        
+        objJSON = [objJSON fieldJSON];
+    end
+    
+    objectPattern = ['{%s' spaceStruct.line '%s}'];
+    json = sprintf(objectPattern, objJSON,spacePrefix);
+end
+
+function [json,bSingleLine] = writeArray(data, spacePrefix, spaceStruct)
+    bSingleLine = false;
+    if ( isnumeric(data) && (size(data,1) == numel(data)) )
+        bSingleLine = true;
+        json = writeSingleLineArray(data, spaceStruct);
+        return;
+    end
+    
+    valSep = ',';
+    arrayJSON = '';
+    
+    valPattern = '%s%s%s';
+    
+    valuePrefix = [spacePrefix spaceStruct.indent];
+    for i=1:size(data,1)
+        if ( i == size(data,1) )
+            valSep = '';
+        end
+        
+        arrayEntry = squashSelect(data, i);
+        if ( numel(arrayEntry) == 1 )
+            if ( iscell(arrayEntry) )
+                valJSON = writeValue(arrayEntry{1}, valuePrefix, spaceStruct);
+            else
+                valJSON = writeValue(arrayEntry, valuePrefix, spaceStruct);
+            end
+        else
+            [valJSON,bSingleLine] = writeArray(arrayEntry, valuePrefix, spaceStruct);
+        end
+        
+        % Combine brackets on one line if all the root array is a single line
+        if ( bSingleLine && (size(data,1) == 1) )
+            json = sprintf('[%s]',valJSON);
+            return
+        else
+            arrayJSON = [arrayJSON sprintf([spaceStruct.line valPattern], valuePrefix, valJSON, valSep)];
+        end
+    end
+    
+    arrayPattern = ['[%s' spaceStruct.line '%s]'];
+    json = sprintf(arrayPattern, arrayJSON,spacePrefix);
+end
+
+function arrayEntry = squashSelect(arrayData, i)
+    if ( ndims(arrayData) == 1 )
+        arrayEntry = arrayData(i);
+        return;
+    end
+    
+    dimSizes = size(arrayData);
+    if ( length(dimSizes) < 3 )
+        dimSizes = [dimSizes 1];
+    end
+    
+    arrayEntry = reshape(arrayData(i,:), dimSizes(2:end));
+end
+
+function json = writeSingleLineArray(data, spaceStruct)
+    valSep = [',' spaceStruct.space];
+    arrayJSON = '';
+    for i=1:length(data)
+        if ( i == length(data) )
+            valSep = '';
+        end
+        
+        valJSON = writeValue(data(i),'', spaceStruct);
+        arrayJSON = [arrayJSON sprintf('%s%s', valJSON,valSep)];
+    end
+    json = sprintf('[%s]', arrayJSON);
+end
+
+function json = writeValue(data, spacePrefix, spaceStruct)
+    if ( ischar(data) )
+        json = sprintf('"%s"',escapeString(data));
+    elseif ( iscell(data) || any(size(data) > 1) )
+        json = writeArray(data, spacePrefix, spaceStruct);
+    elseif ( isstruct(data) )
+        json = writeObject(data, spacePrefix, spaceStruct);
+    elseif ( islogical(data) )
+        if ( data )
+            json = 'true';
+        else
+            json = 'false';
+        end
+    elseif ( isempty(data) )
+        json = 'null';
+    elseif ( isnumeric(data) )
+        json = num2str(data);
+    else
+        ME = MException('json:save','Cannot save unsupported type');
+        ME.throw;
+    end
+end
+
+function quotedStr = escapeString(inStr)
+    escChars = {'\' '"' char(8) char(12) char(10) char(13) char(9)};
+    escStr = {'\\' '\"','\b','\f','\n','\r','\t'};
+    
+    escMap = containers.Map(escChars,escStr);
+    
+    quotedStr = '';
+    for i=1:length(inStr)
+        nextChar = inStr(i);
+        if ( isKey(escMap,nextChar) )
+            nextChar = escMap(nextChar);
+        end
+        
+        quotedStr = [quotedStr nextChar];
+    end
+end
diff --git a/src/MATLAB/+Utils/IndToCoord.m b/src/MATLAB/+Utils/IndToCoord.m
new file mode 100644
index 0000000000000000000000000000000000000000..c9edc7b69d15af289c85832d03b66bc343b169a2
--- /dev/null
+++ b/src/MATLAB/+Utils/IndToCoord.m
@@ -0,0 +1,17 @@
+% IndToCoord - Convert linear array indices into a list of subscript
+% indices.
+% 
+% coords = IndToCoord(arraySize, arrayIdx)
+function coords = IndToCoord(arraySize, arrayIdx)
+    coords = zeros(length(arrayIdx),length(arraySize));
+
+    linSize = [1 cumprod(arraySize)];
+    partialIdx = arrayIdx;
+    for i = length(arraySize):-1:1
+        r = rem(partialIdx-1, linSize(i)) + 1;
+        q = floor((partialIdx-r) / linSize(i)) + 1;
+
+        coords(:,i) = q;
+        partialIdx = r;
+    end
+end
diff --git a/src/MATLAB/+Utils/ParseJSON.m b/src/MATLAB/+Utils/ParseJSON.m
new file mode 100644
index 0000000000000000000000000000000000000000..b4e9f740fc105268e78cf9ed992a04e454e3e22e
--- /dev/null
+++ b/src/MATLAB/+Utils/ParseJSON.m
@@ -0,0 +1,308 @@
+function data = ParseJSON(json)
+    quoteIdx = regexp(json,'\"', 'start');
+    escStart = regexp(json,'\\([/\"\\bfnrt]|u[a-fA-F\d]{4})', 'start');
+    
+    strQuotes = setdiff(quoteIdx,escStart+1);
+    quoteMap = containers.Map(strQuotes(1:end-1),strQuotes(2:end));
+    
+    parsePos = ignoreSpace(json,1);
+    assertChar('parse', {'{','['}, json,parsePos);
+    
+    if ( json(parsePos) == '{' )
+        [data parsePos] = parseObjectJSON(json,parsePos+1, quoteMap);
+    elseif ( json(parsePos) == '[' )
+        [data parsePos] = parseArrayJSON(json,parsePos+1, quoteMap);
+    end
+    
+    data = postprocessObjects(data);
+end
+
+function [objData parsePos] = parseObjectJSON(json, startPos, quoteMap)
+    parsePos = ignoreSpace(json,startPos);
+    
+    objData = [];
+    assertInStr('parseObject', '''STRING'' or ''}''', json, parsePos);
+    if ( json(parsePos) == '}' )
+        parsePos = ignoreSpace(json,parsePos+1);
+        return;
+    end
+    
+    while ( parsePos <= length(json) )
+        assertChar('parseObject', '"', json,parsePos);
+        [keyStr parsePos] = parseStringJSON(json,parsePos, quoteMap);
+        
+        assertChar('parseObject', ':', json,parsePos);
+        [value parsePos] = parseValueJSON(json,parsePos+1, quoteMap);
+%         if ( isempty(value) )
+%             throwError('parseObject','Expecting ''STRING'', ''NUMBER'', ''NULL'', ''TRUE'', ''FALSE'', ''{'', ''[''', json, parsePos);
+%         end
+        
+        objData.(validFieldName(keyStr)) = value;
+        
+        assertInStr('parseObject', '''}''', json, parsePos);
+        if ( json(parsePos) == '}' )
+            parsePos = ignoreSpace(json,parsePos+1);
+            return;
+        end
+        
+        assertChar('parseObject', {',','}'}, json,parsePos);
+        parsePos = ignoreSpace(json,parsePos+1);
+    end
+    
+    throwError('parseObject','Expecting closing ''}''', json, parsePos);
+end
+
+function [arrayData parsePos] = parseArrayJSON(json, startPos, quoteMap)
+    parsePos = ignoreSpace(json,startPos);
+    
+    arrayData = cell(0,1);
+    if ( json(parsePos) == ']' )
+        parsePos = ignoreSpace(json,parsePos+1);
+        return;
+    end
+    
+    while ( parsePos <= length(json) )
+        [value parsePos] = parseValueJSON(json,parsePos, quoteMap);
+%         if ( isempty(value) )
+%             throwError('parseArray','Expecting ''STRING'', ''NUMBER'', ''NULL'', ''TRUE'', ''FALSE'', ''{'', ''[''', json, parsePos);
+%         end
+        
+        arrayData{end+1} = value;
+        
+        assertInStr('parseArray', ''']''', json, parsePos);
+        if ( json(parsePos) == ']' )
+            parsePos = ignoreSpace(json,parsePos+1);
+            return;
+        end
+        
+        assertChar('parseArray', {',',']'}, json,parsePos);
+        
+        parsePos = ignoreSpace(json,parsePos+1);
+    end
+    
+    throwError('parseArray','Expecting closing '']''', json, parsePos);
+end
+
+function [valueData parsePos] = parseValueJSON(json, startPos, quoteMap)
+    parsePos = ignoreSpace(json,startPos);
+    
+    assertInStr('parseValue', '''STRING'', ''NUMBER'', ''NULL'', ''TRUE'', ''FALSE'', ''{'', ''[''', json, parsePos);
+    chkChar = json(parsePos);
+    
+    keywordMap = {'true',true; 'false',false; 'null',[]};
+    
+    switch(chkChar)
+        case '['
+            [valueData parsePos] = parseArrayJSON(json,parsePos+1, quoteMap);
+        case '{'
+            [valueData parsePos] = parseObjectJSON(json,parsePos+1, quoteMap);
+        case '"'
+            [valueData parsePos] = parseStringJSON(json,parsePos, quoteMap);
+        case {'-','0','1','2','3','4','5','6','7','8','9'}
+            [valueData parsePos] = parseNumberJSON(json,parsePos);
+        otherwise
+            for i=1:size(keywordMap,1)
+                [bMatched parsePos] = matchKeyword(json,parsePos,keywordMap{i,1});
+                if ( bMatched )
+                    valueData = keywordMap{i,2};
+                    return;
+                end
+            end
+            throwError('parseValue','Expecting ''STRING'', ''NUMBER'', ''NULL'', ''TRUE'', ''FALSE'', ''{'', ''[''', json, parsePos);
+    end
+end
+
+function [stringData parsePos] = parseStringJSON(json, startPos, quoteMap)
+    if ( ~isKey(quoteMap,startPos) )
+        throwError('parseString','Expecting ''STRING''', json, parsePos);
+    end
+    
+    startQuote = startPos;
+    endQuote = quoteMap(startPos);
+    escapedStr = json(startQuote+1:endQuote-1);
+    
+    stringData = validExpandString(escapedStr, json, startPos);
+    parsePos = ignoreSpace(json,endQuote + 1);
+end
+
+function [numberData parsePos] = parseNumberJSON(json, startPos)
+    numPad = 20;
+    chkEnd = min(startPos+numPad,length(json));
+    
+    matchStr = regexp(json(startPos:chkEnd),'^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?', 'once','match');
+    if ( isempty(matchStr) )
+        throwError('parseNumber','Expecting ''NUMBER''', json, startPos);
+    end
+    
+    numberData = str2double(matchStr);
+    parsePos = ignoreSpace(json,startPos+length(matchStr));
+end
+
+function [bMatched parsePos] = matchKeyword(json,parsePos, keywordStr)
+    bMatched = false;
+    if ( length(json) < parsePos+length(keywordStr) )
+        return;
+    end
+    
+    keywordEnd = parsePos+length(keywordStr)-1;
+    if ( ~strcmpi(json(parsePos:keywordEnd), keywordStr) )
+        return;
+    end
+    
+    parsePos = ignoreSpace(json,keywordEnd+1);
+    bMatched = true;
+end
+
+function fieldStr = validFieldName(inStr)
+    fieldStr = inStr;
+    bAlphaNum = isstrprop(inStr,'alphanum');
+    fieldStr(~bAlphaNum) = '_';
+    
+    if ( ~isletter(fieldStr(1)) )
+        fieldStr = ['s_' fieldStr];
+    end
+end
+
+function expandStr = validExpandString(escapedStr, json,strPos)
+
+    expandSeq = {'\"','\b','\f','\n','\r','\t'};
+    unescStart = regexp(escapedStr,['[' [expandSeq{:}] ']'], 'start');
+    
+    if ( ~isempty(unescStart) )
+        throwError('parseString', 'Invalid character in string', json, strPos+unescStart(1));
+    end
+
+    expandStr = '';
+    bEscape = false;
+    for i=1:length(escapedStr)
+        expandChar = escapedStr(i);
+        
+        if ( bEscape )
+            bEscape = false;
+            if ( expandChar == 'u' )
+                continue;
+            end
+            
+            if ( ~any(expandChar == '\"bfnrt/') )
+                throwError('parseString', 'Invalid escape sequence in string', json, strPos+i);
+            end
+            
+            expandChar = sprintf(['\' expandChar]);
+        elseif ( expandChar == '\' )
+            bEscape = true;
+            continue;
+        end
+        
+        expandStr(end+1) = expandChar;
+    end
+end
+
+function dataOut = postprocessObjects(dataEntry)
+    if ( isstruct(dataEntry) )
+        fields = fieldnames(dataEntry);
+        for i=1:length(fields)
+            dataEntry.(fields{i}) = postprocessObjects(dataEntry.(fields{i}));
+        end
+    elseif ( iscell(dataEntry) )
+        dataEntry = postprocessArrays(dataEntry);
+    end
+    
+    dataOut = dataEntry;
+end
+
+function dataEntry = postprocessArrays(dataEntry)
+    finalDims = length(dataEntry);
+    
+    [bCanExpand expandDims expandTypes] = canExpandArray(dataEntry);
+    if ( ~bCanExpand )
+        finalDims = [finalDims 1];
+    end
+    
+    while ( bCanExpand )
+        dataEntry = reshape(dataEntry, numel(dataEntry),1);
+        dataEntry = vertcat(dataEntry{:});
+        
+        finalDims = [finalDims expandDims];
+        [bCanExpand expandDims expandTypes] = canExpandArray(dataEntry);
+    end
+    
+    dataEntry = reshape(dataEntry, finalDims);
+    if ( length(expandDims) > 1 )
+        for i=1:numel(dataEntry)
+            dataEntry{i} = postprocessObjects(dataEntry{i});
+        end
+    elseif ( length(expandTypes) == 1 && strcmpi(expandTypes{1},'double') )
+        dataEntry = cell2mat(dataEntry);
+    end
+end
+
+function [bCanExpand expandDims expandTypes] = canExpandArray(cellArray)
+    chkType = unique(cellfun(@(x)(class(x)), cellArray(:), 'UniformOutput',0));
+    chkDims = unique(cellfun(@(x)(length(x)), cellArray(:)));
+    
+    bCanExpand = (length(chkDims) == 1) && (length(chkType) == 1) && strcmpi(chkType{1},'cell');
+    
+    expandTypes = chkType;
+    expandDims = chkDims;
+end
+
+function parsePos = ignoreSpace(json,startPos)
+    for parsePos=startPos:length(json)
+        if ( ~isspace(json(parsePos)) )
+            return;
+        end
+    end
+end
+
+function assertInStr(type, expect, json, parsePos)
+    if ( parsePos > length(json) )
+        if ( isempty(expect) )
+            throwError(type, 'Unexpected end of file', json, parsePos);
+        else
+            throwError(type, ['Unexpected end of file, expecting ' expect], json, parsePos);
+        end
+    end
+end
+
+function assertChar(type, charValue, json, parsePos)
+    if ( ischar(charValue) )
+        charValue = {charValue};
+    end
+    
+    if ( parsePos > length(json) )
+        throwError(type, makeAssertStr(charValue), json, parsePos)
+    end
+    
+    for i=1:length(charValue)
+        if ( json(parsePos) == charValue{i} )
+            return;
+        end
+    end
+    
+    throwError(type,makeAssertStr(charValue), json, parsePos)
+end
+
+function errStr = makeAssertStr(charValues)
+    errStr = 'Expecting';
+    
+    for i=1:length(charValues)-1
+        errStr = [errStr ' ''' charValues{i} ''' or'];
+    end
+    
+    errStr = [errStr ' ''' charValues{end} ''''];
+end
+
+function throwError(type,message, json, pos)
+    contextPad = 5;
+    contextStart = max(pos-contextPad,1);
+    contextEnd = min(pos+3*contextPad,length(json));
+    
+    arrowPos = pos-contextStart;
+    arrowSpace = repmat(' ',1,arrowPos);
+    
+    contextStr = json(contextStart:contextEnd);
+    contextStr = strtok(contextStr,sprintf('\n'));
+    
+    ME = MException(['json:' type],'Parse error: %s\n\n%s\n%s^', message,contextStr,arrowSpace);
+    ME.throw;
+end
diff --git a/src/MATLAB/+Utils/PrintTime.m b/src/MATLAB/+Utils/PrintTime.m
new file mode 100644
index 0000000000000000000000000000000000000000..5060ef568877fb436d364ac24ad4675e684eea9e
--- /dev/null
+++ b/src/MATLAB/+Utils/PrintTime.m
@@ -0,0 +1,12 @@
+function [ strOut ] = printTime( timeIn )
+%PRINTTIME takes time in sec and outputs a string in the form HH:MM:SS.ss
+
+hr = floor(timeIn/3600);
+tmNew = timeIn - hr*3600;
+mn = floor(tmNew/60);
+tmNew = tmNew - mn*60;
+sc = tmNew;
+
+strOut = sprintf('%02dh:%02dm:%05.2fs',hr,mn,sc);
+end
+
diff --git a/src/MATLAB/+Utils/SwapXY_RC.m b/src/MATLAB/+Utils/SwapXY_RC.m
new file mode 100644
index 0000000000000000000000000000000000000000..f3d1304a3b7f0e81c10b001dcd4ba414a166500a
--- /dev/null
+++ b/src/MATLAB/+Utils/SwapXY_RC.m
@@ -0,0 +1,9 @@
+function swapCoords = SwapXY_RC(coords)
+    swapCoords = coords;
+    
+    if ( isvector(coords) )
+        swapCoords([1 2]) = swapCoords([2 1]);
+    elseif ( ismatrix(coords) )
+        swapCoords(:,[1 2]) = swapCoords(:,[2 1]);
+    end
+end