diff --git a/+Ecto/compare_manual_ecto.m b/+Ecto/compare_manual_ecto.m
new file mode 100644
index 0000000000000000000000000000000000000000..e4fde8d63e52a9fc3b4c72c2c230e87e9aba4983
--- /dev/null
+++ b/+Ecto/compare_manual_ecto.m
@@ -0,0 +1,84 @@
+function compare_manual_ecto(datasetName, imageRoot)
+    if ( ~exist('Data','dir') )
+        return;
+    end
+    
+    maskInfo = struct('name',{'ecto','endo'}, 'file',{'',''});
+    
+    maskInfo(1).file = fullfile('Data','ectomasks',[datasetName '_ectoregion.mat']);
+    maskInfo(2).file = fullfile('Data','ectomasks',[datasetName '_endoregion.mat']);
+    
+    if ( ~exist(maskInfo(1).file,'file') )
+        warning([datasetName ': Ectoderm mask doesn''t exist -- Skipping']);
+        return;
+    end
+    
+    ncvDir = fullfile('Data', 'ectodeltas');
+    if ( ~exist(ncvDir, 'dir') )
+        mkdir(ncvDir);
+    end
+    
+    jsonFile = fullfile(ncvDir,[datasetName '.json']);
+    if ( exist(jsonFile ,'file') )
+        return;
+    end
+    
+    maskDeltas = struct('type',{}, 'deltas',{}, 'ncvs',{});
+    for i=1:length(maskInfo)
+        if ( ~exist(maskInfo(i).file,'file') )
+            continue;
+        end
+        
+        imPath = getImPath(imageRoot, datasetName, {'matlab_decon','CPPdecon'});
+        im1 = MicroscopeData.Reader(imPath, 'timeRange',[1,1], 'chanList',[1]);
+        
+        s = load(maskInfo(i).file);
+        mask = s.regionInfo.bwMask(:,:,:,1);
+        
+        try
+            [deltas, ncvs] = getNcvDeltas(im1,mask, imPath, 50);
+        catch me
+            warning([datasetName ': Skipping -- ' me.message]);
+            return;
+        end
+        
+        maskDeltas(i).type = maskInfo(i).name;
+        maskDeltas(i).deltas = deltas;
+        maskDeltas(i).ncvs = ncvs;
+    end
+    
+    jsonData = Utils.CreateJSON(maskDeltas);
+    fid = fopen(jsonFile, 'wt');
+    fprintf(fid, '%s\n', jsonData);
+    fclose(fid);
+end
+
+function imPath = getImPath(imageRoot, datasetName, imSubdirs)
+    imPath = fullfile(imageRoot,datasetName);
+    for i=1:length(imSubdirs)
+        if ( exist(fullfile(imPath,imSubdirs{i}), 'dir') )
+            imPath = fullfile(imPath,imSubdirs{i});
+            return;
+        end
+    end
+end
+
+function [deltas, ncvs] = getNcvDeltas(im, mask, imPath, maxFrame)
+    imD = MicroscopeData.ReadMetadataFile(fullfile(imPath,filesep));
+    
+    maxFrame = min(maxFrame, imD.NumberOfFrames);
+    
+    prg = Utils.CmdlnProgress(maxFrame, true, [imD.DatasetName ' -- Computing Deltas']);
+    
+    deltas = NaN(maxFrame,3);
+    ncvs = NaN(maxFrame,1);
+    for t=1:maxFrame
+        imt = MicroscopeData.Reader(imPath, 'timeRange',[t,t], 'chanList',[1]);
+        
+        [chkDelta, chkNCV] = Ecto.get_masked_ncv(im,mask, imt);
+        deltas(t,:) = chkDelta;
+        ncvs(t) = chkNCV;
+        
+        prg.PrintProgress(t);
+    end
+end
diff --git a/+Ecto/ectoSeg.m b/+Ecto/ectoSeg.m
new file mode 100644
index 0000000000000000000000000000000000000000..2a457a2698019189027b8f725853395cf0df80cc
--- /dev/null
+++ b/+Ecto/ectoSeg.m
@@ -0,0 +1,86 @@
+% [im,imD] = MicroscopeData.Reader('H:/Smadar/Raw/22-06-2016_TimeLapse3_DMSO_20hrs_Calcein_FM464/matlab_decon', 'timeRange',[1,1]);
+[im,imD] = MicroscopeData.Reader('C:/Users/mwinter/Documents/Lab/Smadar/Raw/22-06-2016_TimeLapse3_DMSO_20hrs_Calcein_FM464/matlab_decon', 'timeRange',[1,1]);
+
+% addpath('C:\Users\mwinter\Documents\MATLAB\external\AOSLevelsetSegmentationToolboxM');
+pxScale = imD.PixelPhysicalSize;
+
+% mul = 20;
+bwMask = llsm_mask(im);
+
+se = strel('square', 31);
+bwShrink = false(size(bwMask));
+for z=1:size(bwMask,3)
+    bwShrink(:,:,z) = imerode(bwMask(:,:,z), se);
+end
+
+imFM = mat2gray(im(:,:,:,1));
+
+alpha = 0.95;
+thresh = alpha*graythresh(imFM);
+bwFM = imFM >= thresh;
+bwFM(~bwShrink) = false;
+
+% Approximate ectoderm thickness
+ectoDist = 15.0;
+idxPts = find(bwFM);
+coords_xy = Utils.SwapXY_RC(Utils.IndToCoord(size(bwFM), idxPts));
+coords_um = coords_xy.*pxScale;
+
+shp = alphaShape(coords_um(:,1),coords_um(:,2),coords_um(:,3), ectoDist);
+[f,pts_xyz] = boundaryFacets(shp);
+
+unscalePts = pts_xyz ./ pxScale;
+
+poly = D3d.Polygon.MakeEmptyStruct();
+nrms = D3d.Polygon.CalcNorms(unscalePts, f);
+poly(1).index = 1;
+poly(1).frame = 1;
+poly(1).label = '1';
+poly(1).color = [1,0,0];
+poly(1).faces = f;
+poly(1).verts = unscalePts - 0.5;
+poly(1).norms = nrms;
+poly(1).CenterOfMass = mean(unscalePts);
+
+D3d.Viewer.AddPolygons(poly);
+
+% coords = Utils.IndToCoord(size(bwMask), find(bwMask));
+% 
+% init_phi = -ones(size(imFM));
+% ctr = round(mean(coords));
+% padding = 20;
+% padctr = {ctr(1)-padding:ctr(1)+padding, ctr(2)-padding:ctr(2)+padding, ctr(3)-padding:ctr(3)+padding};
+% init_phi(padctr{:}) = 1;
+% 
+% init_phi = ac_reinit(init_phi);
+% 
+% smooth_weight = 3;
+% image_weight = 1e-3; 
+% delta_t = 2;
+% n_iters = 100;
+% show_result = 1;
+% 
+% phi = ac_ChanVese_model(imFM, init_phi, smooth_weight, image_weight, delta_t, n_iters, show_result); 
+
+
+% bwFM = mask;
+% updatePolys(bwFM);
+% 
+% for i=1:500
+%     bwFM = activecontour(imBMask, bwFM, 2, 'edge', 'ContractionBias', -20.0*mul, 'SmoothFactor',mul);
+%     
+%     updatePolys(bwFM);
+% end
+
+function updatePolys(bwFM)
+    rp = regionprops(bwFM, 'PixelList');
+    polys = [];
+    for i=1:length(rp)
+        polys = [polys; D3d.Polygon.Make(rp(i).PixelList, i, ['Endo_' num2str(i)], 1, [1 0 0], 1)];
+    end
+
+    D3d.Viewer.DeleteAllPolygons();
+    D3d.Viewer.AddPolygons(polys);
+    
+    D3d.Update();
+end
diff --git a/+Ecto/get_masked_ncv.m b/+Ecto/get_masked_ncv.m
new file mode 100644
index 0000000000000000000000000000000000000000..3097226ba6a59e906d51efdf3a1221bd358e4096
--- /dev/null
+++ b/+Ecto/get_masked_ncv.m
@@ -0,0 +1,8 @@
+function [delta, ncv] = get_masked_ncv(frame1Im, frame1Ecto, frametIm)
+    frametMask = Segment.llsm_mask(frametIm);
+    
+    [dX,dY,dZ,ncv,overlapSize,ncvMatrixROI] = Registration.FFT.RegisterTwoImages(frame1Im,[],frametIm,[],...
+        [],[],[],[], false, frame1Ecto, frametMask);
+    
+    delta = [dX;dY;dZ];
+end
diff --git a/+Export/get_subdir_list.m b/+Export/get_subdir_list.m
new file mode 100644
index 0000000000000000000000000000000000000000..72b28812d50d47bba3424b56ac6d31ca4ebdf315
--- /dev/null
+++ b/+Export/get_subdir_list.m
@@ -0,0 +1,6 @@
+function subdirs = get_subdir_list(rootDir)
+    flist = dir(rootDir);
+    bValidDir = arrayfun(@(x)((x.isdir>0) && ~strncmpi(x.name,'.',1) && ~strncmpi(x.name,'$',1)), flist);
+
+    subdirs = {flist(bValidDir).name}.';
+end
\ No newline at end of file
diff --git a/+Export/smadar_export_datasets.m b/+Export/smadar_export_datasets.m
new file mode 100644
index 0000000000000000000000000000000000000000..d1fccbd48ff43a5ad82148339d2c7a3b74d8b5a7
--- /dev/null
+++ b/+Export/smadar_export_datasets.m
@@ -0,0 +1,14 @@
+IN_ROOT = 'E:/';
+OUT_ROOT = 'H:/Smadar/Raw';
+
+dateDirs = Export.get_subdir_list(IN_ROOT);
+for i=1:length(dateDirs)
+    dataDirs = Export.get_subdir_list(fullfile(IN_ROOT,dateDirs{i}));
+    for j=1:length(dataDirs)
+        outName = [dateDirs{i} '_' dataDirs{j}];
+        
+        dirIn = fullfile(IN_ROOT, dateDirs{i}, dataDirs{j});
+        dirOut = fullfile(OUT_ROOT, outName);
+        LLSM.ConvertToKLB(dirIn, dirOut, {'matlab_decon', 'CPPdecon', 'Deskewed', 'deskewed'}, outName);
+    end
+end
diff --git a/+Fixup/add_default_center_param.m b/+Fixup/add_default_center_param.m
new file mode 100644
index 0000000000000000000000000000000000000000..7392d64a4c128dd45aef8d7b369ea7bd87b6a46c
--- /dev/null
+++ b/+Fixup/add_default_center_param.m
@@ -0,0 +1,39 @@
+function add_default_center_param(ROOT)
+    if ( ~exist('ROOT','var') )
+        ROOT = pwd();
+    end
+    
+    flist = dir(fullfile(ROOT,'*params.json'));
+    for i=1:length(flist)
+        filepath = fullfile(ROOT,flist(i).name);
+        jsonparams = fileread(filepath);
+        
+        s = jsondecode(jsonparams);
+        fieldList = fieldnames(s);
+        if ( ~all(ismember({'render','view'}, fieldList)) )
+            continue;
+        end
+        
+        if ( ~ismember({'center'},fieldnames(s.view)) )
+            s.view = add_center(s.view);
+        end
+    
+        jsonData = Utils.CreateJSON(s, true);
+        
+%         tok = regexp(flist(i).name, '(.*?)_params\.json', 'once','tokens');
+%         movieName = tok{1};
+        
+%         fid = fopen(fullfile(ROOT,[movieName '_params.json']), 'wt');
+        fid = fopen(filepath,'wt');
+        fprintf(fid, '%s\n', jsonData);
+        fclose(fid);
+    end
+end
+
+
+function newview = add_center(view)
+    newview = view;
+    newview.center = [0.0;0.0;0.0];
+    
+    newview = orderfields(newview, {'zoom','pos','center','worldRot','bClip','clipPlane'});
+end
diff --git a/+Fixup/fixup_im_paths.m b/+Fixup/fixup_im_paths.m
new file mode 100644
index 0000000000000000000000000000000000000000..051e2e8cf62fd2853986d6e0d55a92317005ff38
--- /dev/null
+++ b/+Fixup/fixup_im_paths.m
@@ -0,0 +1,36 @@
+% Load lever files and retarget imageData.imageDir and ui.rootImageFolder
+% NOTE: The special variable <DATASETNAME> is replaced in patterns
+function fixup_im_paths(leverPath, uiImPatterns, rawImPatterns)
+    flist = dir(fullfile(leverPath, '*.LEVER'));
+    for i=1:length(flist)
+        updateConstants(fullfile(leverPath,flist(i).name), uiImPatterns, rawImPatterns);
+    end
+end
+
+function updateConstants(leverFile, uiImPatterns, rawImPatterns)
+    conn = database(leverFile, '','', 'org.sqlite.JDBC', 'jdbc:sqlite:');
+    CONSTANTS = Read.getConstants(conn);
+    
+    datasetName = CONSTANTS.imageData.DatasetName;
+    
+    chkUIPaths = strrep(uiImPatterns, '<DATASETNAME>', datasetName);
+    bUIExist = cellfun(@(x)(exist(x,'dir') > 0), chkUIPaths);
+    
+    validUIPaths = chkUIPaths(bUIExist);
+    if ( isempty(validUIPaths) )
+        error(['No UI image patterns match real directories for dataset: ' datasetName]);
+    end
+    
+    chkRawPaths = strrep(rawImPatterns, '<DATASETNAME>', datasetName);
+    bRawExist = cellfun(@(x)(exist(x,'dir') > 0), chkRawPaths);
+    
+    validRawPaths = chkRawPaths(bRawExist);
+    if ( isempty(validRawPaths) )
+        error(['No raw image patterns match real directories for dataset: ' datasetName]);
+    end
+    
+    CONSTANTS.imageData.imageDir = validRawPaths{1};
+    CONSTANTS.ui.rootImageFolder = validUIPaths{1};
+    
+    Write.updateConstants(conn, CONSTANTS);
+end
diff --git a/+Fixup/fixup_movie_params.m b/+Fixup/fixup_movie_params.m
new file mode 100644
index 0000000000000000000000000000000000000000..59e3bb6bc1d57fb9c109616e9af209ecb9063570
--- /dev/null
+++ b/+Fixup/fixup_movie_params.m
@@ -0,0 +1,38 @@
+function fixup_movie_params(ROOT)
+    if ( ~exist('ROOT','var') )
+        ROOT = pwd();
+    end
+    
+    flist = dir(fullfile(ROOT,'*.mat'));
+    for i=1:length(flist)
+        filepath = fullfile(ROOT,flist(i).name);
+        vlist = whos('-file', filepath);
+        
+        if ( ~all(ismember({'ui','renderparams','viewparams'}, {vlist.name})) )
+            continue;
+        end
+        
+        s = load(filepath);
+        if ( ~ismember({'clipPlane'}, fieldnames(s.viewparams)) )
+            s.viewparams = add_clipinfo(s.viewparams);
+        end
+        
+        outparams = struct('render',{s.renderparams}, 'view',{s.viewparams});
+    
+        jsonData = Utils.CreateJSON(outparams, true);
+
+        [~,movieName,ext] = fileparts(flist(i).name);
+        fid = fopen(fullfile(ROOT,[movieName '_params.json']), 'wt');
+        fprintf(fid, '%s\n', jsonData);
+        fclose(fid);
+    end
+end
+
+
+function newview = add_clipinfo(view)
+    newview = view;
+    newview.bClip = false;
+    newview.clipPlane = 0.0;
+    
+    newview = orderfields(newview, {'zoom','pos','worldRot','bClip','clipPlane'});
+end
diff --git a/+Fixup/fixup_smadar_impaths.m b/+Fixup/fixup_smadar_impaths.m
new file mode 100644
index 0000000000000000000000000000000000000000..749a12bb67d40306de0e6a2f1243366095a9f963
--- /dev/null
+++ b/+Fixup/fixup_smadar_impaths.m
@@ -0,0 +1,6 @@
+rawPatterns = {'E:/Smadar/Raw/<DATASETNAME>/CPPdecon';
+               'E:/Smadar/Raw/<DATASETNAME>/matlab_decon'};
+
+uiPatterns = {'E:/Smadar/LEVERJS/<DATASETNAME>'};
+
+fixup_im_paths('E:/Smadar/LEVERJS', uiPatterns, rawPatterns);
diff --git a/+Fixup/rename_all_frames.m b/+Fixup/rename_all_frames.m
new file mode 100644
index 0000000000000000000000000000000000000000..b8fa4f9503ee688a4a1af40d3131e986e89c63bf
--- /dev/null
+++ b/+Fixup/rename_all_frames.m
@@ -0,0 +1,11 @@
+ROOT='Data';
+
+flist = dir(fullfile(ROOT,'*_frames'));
+
+bValidDirs = arrayfun(@(x)(x.isdir>0), flist);
+frameDirs = flist(bValidDirs);
+
+for i=1:length(frameDirs)
+    movieTok = regexp(frameDirs(i).name, '([-\w]+?)_frames', 'tokens', 'once');
+    rename_frame_files(movieTok{1});
+end
diff --git a/+Fixup/rename_frame_files.m b/+Fixup/rename_frame_files.m
new file mode 100644
index 0000000000000000000000000000000000000000..d25c199c2ac6f35ade67963f8f85103d364d5ab2
--- /dev/null
+++ b/+Fixup/rename_frame_files.m
@@ -0,0 +1,35 @@
+function rename_frame_files(movieName)
+    dataDir = 'Data';
+    
+    %% Setup movie framce-cap dir
+    frameDir = fullfile(dataDir, [movieName '_frames']);
+    if ( ~exist(frameDir, 'dir') )
+        mkdir(frameDir);
+    end
+    
+    flist = dir(fullfile(frameDir,'*.png'));
+    fnames = {flist.name};
+    
+    tok = regexpi(fnames, '([-\w]+?)_t(\d+)\.png','tokens', 'once');
+    
+    bValid = cellfun(@(x)(~isempty(x)), tok);
+    
+    validNames = fnames(bValid);
+    validTok = tok(bValid);
+    
+    ftimes = cellfun(@(x)(str2double(x{2})), validTok);
+    firstT = min(ftimes);
+    
+    if ( firstT == 0 )
+        fprintf('---- %s: Already frame-indexed: skipping\n', frameDir);
+        return;
+    end
+    
+    fprintf('---- %s: Renaming frames\n', frameDir);
+    for i=1:length(validNames)
+        newName = [validTok{i}{1} '_t' num2str(ftimes(i)-firstT,'%04d') '.png'];
+        fprintf('Renaming %s - %s\n', validNames{i},newName);
+        
+        movefile(fullfile(frameDir,validNames{i}), fullfile(frameDir,newName));
+    end
+end
\ No newline at end of file
diff --git a/+LLSM/ConvertToKLB.m b/+LLSM/ConvertToKLB.m
new file mode 100644
index 0000000000000000000000000000000000000000..38691486551e5d85c45bbdeda26beccbf70c8197
--- /dev/null
+++ b/+LLSM/ConvertToKLB.m
@@ -0,0 +1,171 @@
+function ConvertToKLB(dirIn,dirOut, subfolders, forceDatasetName)
+    settingsList = dir(fullfile(dirIn,'*_Settings.txt'));
+    firstMetaSettings = LLSM.ParseSettingsFile(fullfile(dirIn,settingsList(1).name));
+    
+    if ( ~exist('forceDatasetName','var') )
+        forceDatasetName = '';
+    end
+
+    %% Cleanup function to try and avoid partial file-writes
+    fileMap = containers.Map();
+    if ( ~isempty(dirOut) )
+        cleanupObj = onCleanup(@()(cleanupFunc(fileMap)));
+    end
+    
+    for s = 1:length(subfolders)
+        fullOutDir = fullfile(dirOut,subfolders{s});
+        if (~exist(fullfile(dirIn, subfolders{s}),'dir'))
+            warning('Cannot find %s.\nSkipping\n',fullfile(dirIn,subfolders{s}));
+            continue
+        end
+        
+        fprintf('Converting %s\n', fullfile(dirIn,subfolders{s}));
+        if ( ~exist(fullOutDir, 'dir') )
+            mkdir(fullOutDir);
+        end
+        
+        flist = dir(fullfile(dirIn,subfolders{s},'*.tif'));
+        parseStruct = LLSM.ParseFileNames(flist,'*.tif');
+        
+        imDT = MicroscopeData.ReadMetadataFile([fullOutDir filesep]);
+        if ( ~isempty(imDT) )
+            fprintf('Found metadata file for %s... Skipping\n', fullOutDir);
+            continue;
+        end
+        
+        %% TODO: Make this a little more generalized
+        chanMap = unique(vertcat(parseStruct.chan));
+        camMap = unique({parseStruct.cam}.');
+        
+        frames = unique(vertcat(parseStruct.stack));
+        if ( length(frames) == 1 && ~isempty(parseStruct.iter) )
+            frames = unique(parseStruct.iter);
+        end
+        
+        tiffName = LLSM.MatchFilename(parseStruct, camMap(1), frames(1), chanMap(1));
+        chkIm = LLSM.loadtiff(fullfile(dirIn,subfolders{s},[tiffName{1} '.tif']));
+        
+        imSize = size(chkIm);
+        
+        datasetName = parseStruct(1).datasetName;
+        if ( ~isempty(forceDatasetName) )
+            datasetName = forceDatasetName;
+        end
+        
+        %%
+        imD = MicroscopeData.GetEmptyMetadata();
+        imD.DatasetName = datasetName;
+        
+        imD.Dimensions = imSize([2,1,3]);
+        imD.NumberOfChannels = length(camMap);
+        imD.NumberOfFrames = length(frames);
+        imD.PixelFormat = class(chkIm);
+        
+        imD.ChannelColors = [1,1,1; 0,1,0];
+        imD.ChannelNames = {'FM 4-64','Calcein'};
+        imD.PixelPhysicalSize = [0.104, 0.104, firstMetaSettings.zOffset];
+        imD.StartCaptureDate = firstMetaSettings.startCaptureDate;
+        
+        %% Some KLB Helpers for getting block sizing
+        bytes = MicroscopeData.Helper.GetBytesPerVoxel(chkIm);
+        
+        blockSize_xyzct = [64, 64, imSize(3), 1, 1];
+        blockMem = prod(blockSize_xyzct)*bytes;
+        blockSize_xyzct(5) = max(1,floor((1024^2) / blockMem)); % try to get close to 1MB block size
+        
+        myCluster = parcluster('local');
+        threads = myCluster.NumWorkers;
+        
+        %%
+%         poolobj = gcp('nocreate');
+%         if isempty(poolobj)
+%             parpool(2);
+%         end
+        
+        prg = Utils.CmdlnProgress(length(frames), true, sprintf('Reading %s', imD.DatasetName));
+        for t=1:size(frames,1)
+            frameIm = zeros([imSize, length(camMap)]);
+            
+            % Try to skip completed output files
+            fileList = getKLBFiles(imD.DatasetName,fullOutDir,1:length(camMap),[t,t],true,false);
+            bFilesExist = cellfun(@(x)(exist(x,'file') > 0), fileList);
+            if ( all(bFilesExist) )
+                continue;
+            end
+            
+            bReadErr = false;
+            for c=1:length(camMap)
+                tiffName = LLSM.MatchFilename(parseStruct, camMap(c), frames(t), chanMap(1));
+                try
+                    frameIm(:,:,:,c) = LLSM.loadtiff(fullfile(dirIn,subfolders{s},[tiffName{1} '.tif']));
+                catch me
+                    bReadErr = true;
+                    warning(['ERROR reading ''' tiffName{1} '.tif' ''' - skipping: ' me.message]);
+                    break;
+                end
+            end
+            if ( bReadErr )
+                % Skip frame-write if there was an error reading one of the tiffs
+                continue;
+            end
+            
+            fileNameT = sprintf('%s_t%04d.klb', fullfile(fullOutDir,imD.DatasetName), t);
+            
+            wrtFunc = @()(MicroscopeData.KLB.writeKLBstack(frameIm, fileNameT, threads, [0.104 0.104, firstMetaSettings.zOffset, 1,1], blockSize_xyzct));
+            safeWrite(fileMap, fileList, wrtFunc);
+            
+            prg.PrintProgress(t);
+        end
+        
+        MicroscopeData.CreateMetadata(fullOutDir, imD);
+    end
+end
+
+function fileList = getKLBFiles(datasetName,outDir,chanList,timeRange,filePerT,filePerC)
+    fileList = {};
+    
+    fileName = fullfile(outDir,datasetName);
+    if (filePerT)
+        if (filePerC)
+            for t=1:length(timeRange(1):timeRange(2))
+                for c=1:length(chanList)
+                    fileList = [fileList; {sprintf('%s_c%d_t%04d.klb',fileName,chanList(c),(t-1)+timeRange(1))}];
+                end
+            end
+        else
+            for t=1:length(timeRange(1):timeRange(2))
+                fileList = [fileList; {sprintf('%s_t%04d.klb',fileName,(t-1)+timeRange(1))}];
+            end
+        end
+    elseif (filePerC)
+        for c=1:length(chanList)
+            fileList = [fileList; {sprintf('%s_c%d.klb',fileName,chanList(c))}];
+        end
+    else
+        fileList = {[fileName,'.klb']};
+    end
+end
+
+
+function safeWrite(fileMap, fileList, writeFunc)
+    for i=1:length(fileList)
+        fileMap(fileList{i}) = false;
+    end
+    
+    writeFunc();
+    
+    for i=1:length(fileList)
+        remove(fileMap,fileList{i});
+    end
+end
+
+function cleanupFunc(fileMap)
+    k = keys(fileMap);
+    
+    for i=1:length(k)
+        if ( exist(k{i}, 'file') )
+            delete(k{i});
+        end
+    end
+end
+
diff --git a/+LLSM/FixupMetadata.m b/+LLSM/FixupMetadata.m
new file mode 100644
index 0000000000000000000000000000000000000000..817fa093051e0cb68a38aa01674a710cce8d020f
--- /dev/null
+++ b/+LLSM/FixupMetadata.m
@@ -0,0 +1,38 @@
+function FixupMetadata(metadataPath, rawNames)
+    [imD, jsonDir] = MicroscopeData.ReadMetadataFile(metadataPath);
+    if ( isempty(imD) )
+        return
+    end
+    
+    parseStruct = LLSM.ParseFileNames(rawNames);
+    
+    % TODO: This is very specific to older LLSM data names
+    chanMap = unique(vertcat(parseStruct.chan));
+    camMap = unique(vertcat(parseStruct.cam));
+    
+    numFrames = nnz(vertcat(parseStruct.cam)==camMap(1) & vertcat(parseStruct.chan)==chanMap(1));
+    
+    msecDeltas = zeros(numFrames, length(camMap));
+    msecAbs = zeros(numFrames, length(camMap));
+    for c=1:length(camMap)
+        bSelect = (vertcat(parseStruct.cam)==camMap(c) & vertcat(parseStruct.chan)==chanMap(1));
+        msecDeltas(:,c) = vertcat(parseStruct(bSelect).msec);
+        msecAbs(:,c) = vertcat(parseStruct(bSelect).msecAbs);
+    end
+    
+    % TODO: Won't work for actual iterations datasets
+    if ( nnz(msecDeltas(:,1)) < size(msecDeltas,1)-1 )
+        msecDeltas = msecAbs - msecAbs(1,:);
+    end
+    
+    % TODO: Need to handle timestamps better
+    tsDeltas = permute(msecDeltas / 1000, [3,2,1]);
+    imD.StartCaptureDate = datestr(imD.StartCaptureDate, 'yyyy-mm-dd HH:MM:SS');
+    imD.TimeStampDelta = tsDeltas(:,:,1:imD.NumberOfFrames);
+    
+    if ( imD.NumberOfFrames ~= size(tsDeltas,3) )
+        warning(sprintf('%s: Mismatched frames/timestamps truncated timestamps (%d ~= %d)', imD.DatasetName, imD.NumberOfFrames, size(tsDeltas,3)));
+    end
+    
+    MicroscopeData.CreateMetadata(jsonDir, imD);
+end
diff --git a/+LLSM/FixupSmadarData.m b/+LLSM/FixupSmadarData.m
new file mode 100644
index 0000000000000000000000000000000000000000..2bf358f1ec7f955491d48674e0c2d37c78d7eda3
--- /dev/null
+++ b/+LLSM/FixupSmadarData.m
@@ -0,0 +1,42 @@
+function FixupSmadarData(rootDir, infoDir)
+    fList = dir(infoDir);
+    bValidDir = arrayfun(@(x)(~strncmp(x.name, '.',1)&&x.isdir>0), fList);
+    
+    expList = fList(bValidDir);
+    for i=1:length(expList)
+        infoFiles = dir(fullfile(infoDir, expList(i).name, '*.txt'));
+        for j=1:length(infoFiles)
+            tok = regexp(infoFiles(j).name, '(.*)_flist\.txt', 'tokens','once');
+            if ( isempty(tok) )
+                continue;
+            end
+            datasetName = [expList(i).name '_' tok{1}];
+            
+            if ( ~exist(fullfile(rootDir, datasetName), 'dir') )
+                continue;
+            end
+            
+            updateSubdirs(fullfile(rootDir, datasetName), datasetName, fullfile(infoDir, expList(i).name, infoFiles(j).name));
+        end
+    end
+end
+
+function updateSubdirs(dataDir, datasetName, infoFile)
+    fprintf('Updating %s ...\n', dataDir);
+    orgFilenames = importdata(infoFile);
+
+    fList = dir(dataDir);
+    bValidDir = arrayfun(@(x)(~strncmp(x.name, '.',1)&&x.isdir>0), fList);
+    
+    dataSubdirs = fList(bValidDir);
+    for i=1:length(dataSubdirs)
+        imDir = [fullfile(dataDir,dataSubdirs(i).name) filesep];
+        LLSM.FixupMetadata(imDir, orgFilenames);
+%         LLSM.RenameDataset(imDir, datasetName);
+%         
+%         if ( endsWith(dataSubdirs(i).name,'KLB') )
+%             dropKLB = dataSubdirs(i).name(1:end-3);
+%             movefile(fullfile(dataDir,dataSubdirs(i).name),fullfile(dataDir,dropKLB));
+%         end
+    end
+end
diff --git a/+LLSM/MatchFilename.m b/+LLSM/MatchFilename.m
new file mode 100644
index 0000000000000000000000000000000000000000..d7a73835605ac4c4ba22d32050d119b6df3247a4
--- /dev/null
+++ b/+LLSM/MatchFilename.m
@@ -0,0 +1,70 @@
+function outNames = MatchFilename(parseStruct, camera,frame,channel)
+    outNames = {};
+
+    bValidStacks = checkNonemptyField(parseStruct, 'stack');
+    if (bValidStacks)
+        useStacks = true;
+        numStacks = max(vertcat(parseStruct.stack))+1;
+    else
+        useStacks = false;
+        numStacks = [];
+    end
+    
+    bValidIter = checkNonemptyField(parseStruct, 'iter');
+    if ( bValidIter )
+        useIter = true;
+    else
+        useIter = false;
+    end
+
+    bValidCam = checkNonemptyField(parseStruct, 'cam');
+    if ( bValidCam )
+        unqCams = unique({parseStruct.cam}.');
+        if (length(unqCams)>1)
+            useCams = true;
+        else
+            if ( ~isempty(camera) && all(unqCams~=camera) )
+                return
+            end
+            useCams = false;
+        end
+    else
+        if ( ~isempty(camera) )
+            return
+        end
+        useCams = false;
+    end
+    
+    if ( useIter )
+        if ( useStacks )
+            iterIdx = floor((frame) / numStacks);
+            stackIdx = frame - iterIdx*numStacks;
+            timeMask = (vertcat(parseStruct.stack) == stackIdx) & (vertcat(parseStruct.iter) == iterIdx);
+        else
+            timeMask = (vertcat(parseStruct.iter) == frame);
+        end
+    else
+        timeMask = (vertcat(parseStruct.stack) == frame);
+    end
+    
+    name = {};
+    if ( ~isempty(camera) && useCams )
+        camChanMask = ( strcmpi(camera,{parseStruct.cam}.') & (vertcat(parseStruct.chan)==channel) );
+        if (any(camChanMask))
+            name = {parseStruct(timeMask & camChanMask).name}.';
+        end
+    elseif (~isempty(channel))
+        chanMask = (vertcat(parseStruct.chans) == channel);
+        if (any(chanMask))
+            name = {parseStruct(timeMask & chanMask).name}.';
+        end
+    else
+        name ={parseStruct(timeMask).name}.';
+    end
+    
+    outNames = name;
+end
+
+function bNonempty = checkNonemptyField(parseStruct, fieldname)
+    bNonempty = all(arrayfun(@(x)(~isempty(x.(fieldname))), parseStruct));
+end
diff --git a/+LLSM/RenameDataset.m b/+LLSM/RenameDataset.m
new file mode 100644
index 0000000000000000000000000000000000000000..f2ee420a541f979430d4d19dd6cfc5658f7b2a0c
--- /dev/null
+++ b/+LLSM/RenameDataset.m
@@ -0,0 +1,28 @@
+function RenameDataset(imPath, datasetName)
+    [imD, jsonDir, jsonFile] = MicroscopeData.ReadMetadataFile(imPath);
+    if ( isempty(imD) )
+        return
+    end
+    
+    oldDatasetName = imD.DatasetName;
+    
+    oldFile = fullfile(jsonDir,jsonFile);
+    delete(oldFile);
+    
+    imD.DatasetName = datasetName;
+    MicroscopeData.CreateMetadata(imPath, imD);
+    
+    flist = dir(fullfile(imPath, [oldDatasetName '*.klb']));
+    
+    inNames = {flist.name}.';
+    guardName = regexptranslate('escape', oldDatasetName);
+    outNames = regexprep(inNames, [guardName '(.*)'], [datasetName '$1']);
+    
+    for i=1:length(outNames)
+        if ( strcmp(inNames{i},outNames{i}) )
+            continue;
+        end
+%         fprintf('%s --> %s\n', inNames{i}, outNames{i});
+        movefile(fullfile(imPath,inNames{i}), fullfile(imPath,outNames{i}));
+    end
+end
diff --git a/+Movie/movie_enable_uitoolbars.m b/+Movie/movie_enable_uitoolbars.m
new file mode 100644
index 0000000000000000000000000000000000000000..5064e432dffc9cd65352579d41f7a11641188426
--- /dev/null
+++ b/+Movie/movie_enable_uitoolbars.m
@@ -0,0 +1,20 @@
+function movie_enable_uitoolbars(bEnable, uiserver_info)
+    if ( ~exist('uiserver_info','var') )
+        web_opts = weboptions('Timeout',60);
+        uiserver_info = struct('host','http://localhost:4444', ...
+                            'opts',web_opts);
+    end
+    uiserver_info.opts.MediaType = 'application/json';
+    
+    ui = webread([uiserver_info.host '/ui'],uiserver_info.opts);
+    
+    toolbarDisplayMode = 'none';
+    if ( bEnable )
+        toolbarDisplayMode = 'block';
+    end
+    
+    ui.sidebar = toolbarDisplayMode;
+    ui.webToolbar = toolbarDisplayMode;
+    
+    webwrite([uiserver_info.host '/ui'],uiserver_info.opts, ui);
+end
\ No newline at end of file
diff --git a/+Movie/movie_hist_match.m b/+Movie/movie_hist_match.m
new file mode 100644
index 0000000000000000000000000000000000000000..5dbbd87dcea90eb60485383c4552254a9ca87b0b
--- /dev/null
+++ b/+Movie/movie_hist_match.m
@@ -0,0 +1,332 @@
+function movie_hist_match(movieName, matchFrame, startFrame, endFrame, match_type)
+    dataDir = 'Data';
+    
+    if ( ~any(strcmpi(match_type, {'ks','mdpa'})) )
+        error('Invalid matching type');
+    end
+    
+    %% Setup movie frame-cap dir
+    frameDir = fullfile(dataDir, [movieName '_frames']);
+    if ( ~exist(frameDir, 'dir') )
+        mkdir(frameDir);
+    end
+    
+    %% Setup server info and options to pass around
+    web_opts = weboptions('Timeout',60, 'MediaType','application/json');
+    uiserver_info = struct('host','http://localhost:4444', ...
+                        'opts',web_opts);
+    
+	%% Load constant info
+    CONSTANTS = webread([uiserver_info.host '/CONSTANTS'], uiserver_info.opts);
+    imD = CONSTANTS.imageData;
+    imD.Dimensions = imD.Dimensions';
+    
+    %% Put frame start/end and matching info in the histogram params file (for reference)
+    movieInfo = struct('startFrame',{startFrame}, 'endFrame',{endFrame}, 'matchFrame',{matchFrame});
+    
+    %% Assume current transfer function is target (and get params)
+    renderparams = webread([uiserver_info.host '/renderparams'], uiserver_info.opts);
+    viewparams = webread([uiserver_info.host '/view'], uiserver_info.opts);
+    
+    mvparams = struct('render',{renderparams}, 'view',{viewparams});
+    mvparams.movie = movieInfo;
+    
+    %% Load match-frame and make transformed histogram
+    im = MicroscopeData.Reader('imageData',imD, 'timeRange',[matchFrame,matchFrame]);
+%     [rawHist,centers] = computeRawHist(im);
+%     matchhsts = computeTfmHistIm(im, mvparams.render);
+
+    [rhst,hstc] = computeRawHist(im);
+    thsts = transformHist(rhst,hstc, mvparams.render);
+%     thsts = transformHistGauss(rhst,hstc, mvparams.render);
+    
+    %% Load all other frames and try to match histogram
+    mvparams.frametfm = cell(endFrame,1);
+    prg = Utils.CmdlnProgress(endFrame-startFrame+1,[],'Matching Histogram');
+    for t=startFrame:endFrame
+        if ( t == matchFrame )
+            continue;
+        end
+        
+        im = MicroscopeData.Reader('imageData',imD, 'timeRange',[t,t]);
+        matchparams = search_params_match(im, thsts, mvparams.render, match_type);
+        mvparams.frametfm{t} = matchparams;
+        
+        prg.PrintProgress(t-startFrame+1);
+    end
+    
+    %% Save matched params
+    fid = fopen(fullfile(frameDir, [movieName '_histp_' match_type '.json']), 'wt');
+    fprintf(fid, '%s\n', Utils.CreateJSON(mvparams, true));
+    fclose(fid);
+end
+
+function matchparams = search_params_match(im, matchhsts, initparams, match_type)
+    matchparams = [];
+    
+    if ( strcmpi(match_type,'ks') )
+        cmp_func = @ksdist;
+    elseif ( strcmpi(match_type,'mdpa') )
+        cmp_func = @mdpa;
+    end
+    
+    [rhst,hstc] = computeRawHist(im);
+    
+    stepsz = 0.05;
+    xmin = (0:stepsz:(1.0-stepsz)).';
+    xmax = (stepsz:stepsz:1.0).';
+    ymid = (stepsz:stepsz:(1.0-stepsz)).';
+    for c=1:size(matchhsts,1)
+%         ymid = clamp(ymid, 0.01,0.99);
+        
+        [xxmin,yymid,xxmax] = meshgrid(xmin,ymid,xmax);
+        
+        xxmin = xxmin(:);
+        xxmax = xxmax(:);
+        
+        bValid = (xxmax-xxmin) >= stepsz;
+        xxmin = xxmin(bValid);
+        xxmax = xxmax(bValid);
+        
+        xxmid = (xxmin+xxmax)/2;
+        
+        yymid = yymid(bValid);
+        
+        d = Inf(size(xxmin,1), 1);
+        paramInfo = [];
+        for i=1:size(xxmin,1)
+            A = [xxmin(i)^2 xxmin(i) 1; xxmid(i)^2 xxmid(i) 1; xxmax(i)^2 xxmax(i) 1];
+            p = A \ [0; yymid(i); 1];
+            
+            pa = p(1);
+            pb = p(2);
+            pc = p(3);
+            
+            pmin = xxmin(i);
+            pmax = xxmax(i);
+            
+            if (pa > 0)
+                pmin = max(-pb/(2*pa), pmin);
+            elseif (pa < 0)
+                pmax = min(-pb/(2*pa), pmax);
+            end
+            
+            params = struct('a',{pa}, 'b',{pb}, 'c',{pc}, 'minRange',{pmin}, 'maxRange',{pmax});
+            %% TODO - Fix this!!!!!!
+%             thst = resampleTfmHistGauss(rhst(c,:), hstc, pa,pb,pc,pmin,pmax);
+            thst = resampleTfmHist(rhst(c,:), hstc, pa,pb,pc,pmin,pmax);
+%             thst = computeTfmHistIm(im, params, c);
+            d(i) = cmp_func(thst, matchhsts(c,:));
+%             d(i) = mdpa(thst, matchhsts(c,:));
+%             d(i) = ksdist(thst, matchhsts(c,:));
+            paramInfo = [paramInfo; params];
+        end
+        
+        [~,bestIdx] = min(d);
+        bestParams = paramInfo(bestIdx);
+        matchparams = [matchparams; bestParams];
+    end
+end
+
+function d = ksdist(h1,h2)
+    F1 = cumsum(h1);
+    F2 = cumsum(h2);
+    
+    d = max(abs(F2-F1));
+end
+
+% Mimimum distance of pair assignments (Cha and Srihari, Pattern Recognition 2002)
+function d = mdpa(h1,h2)
+    dH = cumsum(h1-h2);
+    d = sum(abs(dH));
+end
+
+function thst = computeTfmHistIm(data, tfmParams, chans)
+    tfmBins = 256;
+    if ( ~exist('chans','var') )
+        chans = 1:size(data,4);
+    end
+    
+    tfmIdx = 1:length(chans);
+    if ( length(tfmParams) == 1 )
+        tfmIdx = ones(1,length(chans));
+    end
+    
+    thst = zeros(length(chans), tfmBins);
+    for i = 1:length(chans)
+        ci = chans(i);
+        ti = tfmIdx(i);
+        
+        tmin = tfmParams(ti).minRange;
+        tmax = tfmParams(ti).maxRange;
+        
+        a = tfmParams(ti).a;
+        b = tfmParams(ti).b;
+        c = tfmParams(ti).c;
+        
+        d = mat2gray(data(:,:,:,ci));
+        
+        tv = clamp(d, tmin,tmax);
+        tvals = clamp(a*tv.^2 + b*tv + c, 0,1);
+        
+        thst(i,:) = histcounts(tvals, tfmBins);
+    end
+end
+
+function [hst,c] = computeRawHist(data)
+    startBins = 8192;
+    numCh = size(data,4);
+    hst = zeros(numCh, startBins);
+    for i = 1:size(data,4)
+        d = mat2gray(data(:));
+        hst(i,:) = histcounts(d, startBins);
+    end
+    
+    spc = 1 / startBins;
+    c = (0.5*spc):spc:1.0;
+end
+
+function thst = transformHist(inhst, hstc, tfmParams)
+    thst = [];
+    for i=1:size(inhst,1)
+        xmin = tfmParams(i).minRange;
+        xmax = tfmParams(i).maxRange;
+        
+        a = tfmParams(i).a;
+        b = tfmParams(i).b;
+        c = tfmParams(i).c;
+        
+        chst = resampleTfmHist(inhst(i,:), hstc, a,b,c,xmin,xmax);
+        thst = [thst; chst];
+    end
+end
+
+function thst = transformHistGauss(inhst, hstc, tfmParams)
+    thst = [];
+    for i=1:size(inhst,1)
+        xmin = tfmParams(i).minRange;
+        xmax = tfmParams(i).maxRange;
+        
+        a = tfmParams(i).a;
+        b = tfmParams(i).b;
+        c = tfmParams(i).c;
+        
+        chst = resampleTfmHistGauss(inhst(i,:), hstc, a,b,c,xmin,xmax);
+        thst = [thst; chst];
+    end
+end
+
+% function thst = computeTfmHist(rawHist, hctrs, tfmParams)
+%     tfmBins = 256;
+%     
+%     tspc = 1 / tfmBins;
+%     ledge = (0:(tfmBins-1)) * tspc;
+%     redge = (1:tfmBins) * tspc;
+%     
+%     % HACK - Makes sure 0 or 1 value maps into output histogram space
+%     ledge(1) = -1;
+%     redge(end) = 2;
+%     
+%     thst = zeros(size(rawHist,1), tfmBins);
+%     for i=1:size(rawHist,1)
+%         tmin = tfmParams(i).minRange;
+%         tmax = tfmParams(i).maxRange;
+%         
+%         a = tfmParams(i).a;
+%         b = tfmParams(i).b;
+%         c = tfmParams(i).c;
+%         
+%         tc = clamp(hctrs, tmin,tmax);
+%         tctrs = clamp(a*tc.^2 + b*tc + c, 0,1);
+%         
+%         cmt = repmat(tctrs, tfmBins,1);
+%         bbins = (cmt > ledge.') & (cmt <= redge.');
+%         
+%         cnts = repmat(rawHist(i,:), tfmBins,1);
+%         n = zeros(size(cnts));
+%         n(bbins) = cnts(bbins);
+%         
+%         thst(i,:) = sum(n,2).';
+%     end
+% end
+
+% function hst = resampleTfmHist(inhst, hstc, a,b,c,xmin,xmax)
+%     tfmBins = 256;
+%     
+%     tspc = 1 / tfmBins;
+%     ledge = ((0:(tfmBins-1)) * tspc).';
+%     redge = ((1:tfmBins) * tspc).';
+%     
+%     if ( abs(a) < 1e-6 )
+%         iledge = (ledge - c) / b;
+%         iredge = (redge - c) / b;
+%     else
+%         iledge = (-b + sqrt(b^2 - 4*a*(c-ledge))) / (2*a);
+%         iredge = (-b + sqrt(b^2 - 4*a*(c-redge))) / (2*a);
+%     end
+%     
+%     %% Hack to make far left/right bins eat everything else
+%     iledge(1) = -1;
+%     iredge(end) = 2;
+%     
+%     repc = repmat(hstc, tfmBins,1);
+%     bbins = (repc > iledge) & (repc <= iredge);
+%     
+%     reph = repmat(inhst, tfmBins,1);
+%     n = zeros(size(reph));
+%     n(bbins) = reph(bbins);
+%     
+%     hst = (sum(n,2).');
+% end
+
+function hst = resampleTfmHist(inhst, hstc, a,b,c,xmin,xmax)
+    tfmBins = 256;
+    
+    tspc = 1 / tfmBins;
+    ledge = ((0:(tfmBins-1)) * tspc).';
+    redge = ((1:tfmBins) * tspc).';
+    
+    %% Hack to make far left/right bins eat everything else
+    ledge(1) = -1;
+    redge(end) = 2;
+    
+    hcc = clamp(hstc, xmin,xmax);
+    tfmc = clamp(a*hcc.^2 + b*hcc + c, 0,1);
+    
+    repc = repmat(tfmc, tfmBins,1);
+    bbins = (repc > ledge) & (repc <= redge);
+    
+    reph = repmat(inhst, tfmBins,1);
+    n = zeros(size(reph));
+    n(bbins) = reph(bbins);
+    
+    hst = (sum(n,2).');
+end
+
+function hst = resampleTfmHistGauss(inhst, hstc, a,b,c,xmin,xmax)
+    tfmBins = 256;
+    
+    tspc = 1 / tfmBins;
+    ledge = ((0:(tfmBins-1)) * tspc).';
+    redge = ((1:tfmBins) * tspc).';
+    
+    smean = (ledge + redge) / 2;
+    ssig = tspc;
+    
+    hcc = clamp(hstc, xmin,xmax);
+    tfmc = clamp(a*hcc.^2 + b*hcc + c, 0,1);
+    
+    repc = repmat(tfmc, tfmBins,1);
+    reph = repmat(inhst, tfmBins,1);
+    
+    sval = exp(-0.5*(repc-smean).^2 / ssig^2);
+    sw = sum(sval(:)) / tfmBins;
+    
+    sval = ((length(inhst) / tfmBins) / sw) * sval;
+    
+    hst = (sum(reph .* sval, 2)).';
+end
+
+function cv = clamp(v, minv,maxv)
+    cv = max(min(v, maxv), minv);
+end
diff --git a/+Movie/render_matched_movie.m b/+Movie/render_matched_movie.m
new file mode 100644
index 0000000000000000000000000000000000000000..ecd812c3c13880d4f17af71aa45be155db1f5415
--- /dev/null
+++ b/+Movie/render_matched_movie.m
@@ -0,0 +1,135 @@
+function render_matched_movie(movieName, match_type)
+    dataDir = 'Data';
+    
+    %% Check for match_type info json
+    prvFrameDir = fullfile(dataDir, [movieName '_frames']);
+    jsonFile = fullfile(prvFrameDir, [movieName '_histp_' match_type '.json'] );
+    if ( ~exist(prvFrameDir, 'dir') )
+        warning([jsonFile ' json not found']);
+        return;
+    end
+    
+    if ( ~exist(jsonFile, 'file') )
+        warning([jsonFile ' json not found']);
+        return;
+    end
+    
+    
+    %% Setup output frame-cap dir
+    frameDir = fullfile(dataDir, [movieName '_' match_type '_frames']);
+    if ( ~exist(frameDir, 'dir') )
+        mkdir(frameDir);
+    end
+    
+    movieInfo = jsondecode(fileread(jsonFile));
+    movieInfo.movieName = movieName;
+    movieInfo.frameDir = frameDir;
+    
+    %% Setup server info and options to pass around
+    web_opts = weboptions('Timeout',60, 'MediaType','application/json');
+    uiserver_info = struct('host','http://localhost:4444', 'opts',web_opts);
+    
+    %% Render all frames to folder
+    renderframes(movieInfo, uiserver_info);
+    
+    Movie.movie_enable_uitoolbars(true, uiserver_info);
+    
+%     %% Load constant info
+%     CONSTANTS = webread([uiserver_info.host '/CONSTANTS'], uiserver_info.opts);
+%     
+%     %% TODO: FFMPEG encode
+%     samplerate = diff(squeeze(CONSTANTS.imageData.TimeStampDelta(1,1,:)));
+%     asr = median(samplerate);
+%     
+%     fps = 30;
+%     minrate = 5;
+%     
+%     framerate = 12 * (6 / (asr));
+%     framerate = max(framerate, minrate);
+%     
+%     inpattern = fullfile(frameDir,[movieName '_t%04d.png']);
+%     movieFile = [movieName '_fps' num2str(round(framerate)) '.mp4'];
+%     
+%     ffmpegStr = sprintf('ffmpeg -y -framerate %4.2f -i %s -pix_fmt yuv420p -r %d %s', framerate, inpattern, fps, movieFile);
+%     status = system(ffmpegStr);
+end
+
+function renderframes(movieInfo, uiserver_info)
+    movieName = movieInfo.movieName;
+    frameDir = movieInfo.frameDir;
+    
+    renderInfo = movieInfo.render;
+    frametfm = movieInfo.frametfm;
+    
+    %% Disable leverjs toolbars and set movie rendering parameters
+    Movie.movie_enable_uitoolbars(false, uiserver_info);
+    pause(0.5);
+    
+    %% Try to wait for last initial-render to complete
+    if ( ~awaitRenderDone(300, uiserver_info) )
+        error('Full rendering timed-out after 5 minutes!');
+    end
+    
+    pause(0.5);
+    
+    baseRender = renderInfo;
+    viewInfo = movieInfo.view;
+    
+    maxFrame = length(frametfm);
+    for i=1:maxFrame
+        renderInfo = baseRender;
+        
+        if ( ~isempty(frametfm{i}) )
+            for c=1:length(renderInfo)
+                renderInfo(c).a = frametfm{i}(c).a;
+                renderInfo(c).b = frametfm{i}(c).b;
+                renderInfo(c).c = frametfm{i}(c).c;
+                renderInfo(c).minRange = frametfm{i}(c).minRange;
+                renderInfo(c).maxRange = frametfm{i}(c).maxRange;
+            end
+        end
+        
+        set_all_params(i, renderInfo, viewInfo, uiserver_info);
+        
+        %% Wait for complete volume render
+        if ( ~awaitRenderDone(300, uiserver_info) )
+            error('Full rendering timed-out after 5 minutes!');
+        end
+        
+        %% Write frame to directory
+        imFrame = webread([uiserver_info.host '/screenCap'], uiserver_info.opts);
+        
+        frameSize = [size(imFrame,1) size(imFrame,2)];
+        
+        framePad = mod(frameSize,2);
+        imPad = zeros([frameSize + framePad, 3], 'like',imFrame);
+        
+        imPad(1:frameSize(1),1:frameSize(2),:) = imFrame;
+        
+        frameFile = fullfile(frameDir, [movieName '_t' num2str(i-1, '%04d') '.png']);
+        imwrite(imPad, frameFile, 'png');
+        
+    end
+end
+
+function set_all_params(frame, renderInfo, viewInfo, uiserver_info)
+    webwrite([uiserver_info.host '/renderparams'],uiserver_info.opts, renderInfo);
+    webwrite([uiserver_info.host '/view'],uiserver_info.opts, viewInfo);
+    webwrite([uiserver_info.host '/time/' num2str(frame)] ,'');
+end
+
+function bCompleted = awaitRenderDone(timeout, uiserver_info)
+    bCompleted=false;
+    
+    elapsed = 0;
+    chk_timer = tic();
+    while ( ~bCompleted && (timeout <= 0 || elapsed <= timeout) )
+        bCompleted = webread([uiserver_info.host '/drawComplete'], uiserver_info.opts);
+        if ( bCompleted )
+            return;
+        end
+        
+        pause(0.5);
+        elapsed = toc(chk_timer);
+    end
+end
diff --git a/+Movie/render_small_movie.m b/+Movie/render_small_movie.m
new file mode 100644
index 0000000000000000000000000000000000000000..33fa6c1c209c2b8bd1c5f45646e404758b2f9939
--- /dev/null
+++ b/+Movie/render_small_movie.m
@@ -0,0 +1,32 @@
+function render_small_movie(movieName, asr, size_target_mb, resx)
+%     samplerate = diff(squeeze(CONSTANTS.imageData.TimeStampDelta(1,1,:)));
+%     asr = median(samplerate);
+    frameDir = fullfile('Data', [movieName '_frames']);
+    nframes = length(dir(fullfile(frameDir,[movieName '*.png'])));
+    
+    if ( nframes <= 0 )
+        error('No video frames found!');
+    end
+
+    fps = 30;
+    minrate = 5;
+    
+    framerate = 12 * (6 / (asr));
+    framerate = max(framerate, minrate);
+    
+    duration = fps * nframes;
+    bitrate = round(8192 * size_target_mb / duration);
+    
+    inpattern = fullfile(frameDir,[movieName '_t%04d.png']);
+    movieFile = fullfile('final_movies', 'small_movies', [movieName '_small_fps' num2str(round(framerate)) '.mp4']);
+    
+%     ffmpegP1Str = sprintf('ffmpeg -y -framerate %4.2f -i %s -vf scale=%d:-1 -c:v libx264 -b:v %dk -pass 1 -pix_fmt yuv420p -r %d -an -f mp4 NUL', framerate, inpattern, resx, bitrate, fps);
+%     status = system(ffmpegP1Str);
+%     
+%     ffmpegP2Str = sprintf('ffmpeg -y -framerate %4.2f -i %s -vf scale=%d:-1 -c:v libx264 -b:v %dk -pass 2 -pix_fmt yuv420p -r %d %s', framerate, inpattern, resx, bitrate, fps, movieFile);
+% %     ffmpegP2Str = sprintf('ffmpeg -y -i %s -c:v libx264 -b:v %dk -pass 2 %s', inpattern, bitrate, movieFile);
+%     status = system(ffmpegP2Str);
+    
+    ffmpegStr = sprintf('ffmpeg -y -framerate %4.2f -i %s -vf scale=%d:-1 -preset veryslow -crf 28 -pix_fmt yuv420p -r %d %s', framerate, inpattern, resx, fps, movieFile);
+    status = system(ffmpegStr);
+end
diff --git a/+Movie/rendermovie.m b/+Movie/rendermovie.m
new file mode 100644
index 0000000000000000000000000000000000000000..b6de1a6868beb998832581cfc44513d7468e5cbc
--- /dev/null
+++ b/+Movie/rendermovie.m
@@ -0,0 +1,138 @@
+function rendermovie(movieName, startFrame, endFrame)
+    dataDir = 'Data';
+    
+    %% Setup movie framce-cap dir
+    frameDir = fullfile(dataDir, [movieName '_frames']);
+    if ( ~exist(frameDir, 'dir') )
+        mkdir(frameDir);
+    end
+    
+    %% Setup server info and options to pass around
+    web_opts = weboptions('Timeout',60, 'MediaType','application/json');
+    uiserver_info = struct('host','http://localhost:4444', ...
+                        'opts',web_opts);
+
+	
+	
+    %% Disable leverjs toolbars and set movie rendering parameters
+    Movie.movie_enable_uitoolbars(false, uiserver_info);
+    Movie.set_movie_params(movieName, uiserver_info);
+    
+    pause(0.5);
+    
+    %% Try to wait for last initial-render to complete
+    if ( ~awaitRenderDone(300, uiserver_info) )
+        error('Full rendering timed-out after 5 minutes!');
+    end
+    
+    pause(0.5);
+    
+    %% Load constant info
+    CONSTANTS = webread([uiserver_info.host '/CONSTANTS'], uiserver_info.opts);
+    
+    samplerate = diff(squeeze(CONSTANTS.imageData.TimeStampDelta(1,1,:)));
+    asr = median(samplerate);
+    
+    %% Put frame start/end info in the movie-params file (for reference)
+    movieInfo = struct('startFrame',{startFrame}, 'endFrame',{endFrame}, 'avgSampleRate',{asr});
+    
+    mvParams = jsondecode(fileread(fullfile(dataDir,[movieName '_params.json'])));
+    mvParams.movie = movieInfo;
+    
+    fid = fopen(fullfile(dataDir, [movieName '_params.json']), 'wt');
+    fprintf(fid, '%s\n', Utils.CreateJSON(mvParams, true));
+    fclose(fid);
+    
+	%% Compare current params to file-params
+%     if ( ~cmp_movie_params(movieName) )
+%         % Delete all old frames if movie params have changed
+%         delete(fullfile(frameDir,'*.*'));
+%     end
+    paramfname = [movieName '_params.json'];
+    if ( exist(fullfile(frameDir,paramfname),'file') )
+        movieJSON = strtrim(fileread(fullfile(frameDir,paramfname)));
+        paramJSON = strtrim(fileread(fullfile(dataDir,paramfname)));
+        if ( ~strcmpi(movieJSON,paramJSON) )
+            button = questdlg('','','Continue','Cancel','Cancel');
+            if ( strcmpi(button,'Cancel') )
+                return;
+            else
+                % Delete all old frames if movie params have changed
+                delete(fullfile(frameDir,'*.*'));
+                copyfile(fullfile(dataDir,paramfname), fullfile(frameDir,paramfname));
+            end
+        end
+    else
+        copyfile(fullfile(dataDir,paramfname), fullfile(frameDir,paramfname));
+    end
+
+    %% Render movie frames
+    startFrame = max(1, startFrame);
+    endFrame = min(CONSTANTS.imageData.NumberOfFrames, endFrame);
+    
+    for time=startFrame:endFrame
+        % Use frame-index for image output (0-based)
+        frmIdx = time - startFrame;
+        %% Skip frames that have already been written
+        frameFile = fullfile(frameDir, [movieName '_t' num2str(frmIdx, '%04d') '.png']);
+        if ( exist(frameFile,'file') )
+            continue;
+        end
+        
+        %% Set frame time
+        webwrite([uiserver_info.host '/time/' num2str(time)] ,'');
+
+        %% Wait for complete volume render
+        if ( ~awaitRenderDone(300, uiserver_info) )
+            error('Full rendering timed-out after 5 minutes!');
+        end
+        
+        %% Write frame to directory
+        imFrame = webread([uiserver_info.host '/screenCap'], web_opts);
+        
+        frameSize = [size(imFrame,1) size(imFrame,2)];
+        
+        framePad = mod(frameSize,2);
+        imPad = zeros([frameSize + framePad, 3], 'like',imFrame);
+        
+        imPad(1:frameSize(1),1:frameSize(2),:) = imFrame;
+        
+        imwrite(imPad, frameFile, 'png');
+    end
+    
+    Movie.movie_enable_uitoolbars(true, uiserver_info);
+    
+    %% TODO: FFMPEG encode
+    fps = 30;
+    minrate = 5;
+    
+    framerate = 12 * (6 / (asr));
+    framerate = max(framerate, minrate);
+    
+    inpattern = fullfile(frameDir,[movieName '_t%04d.png']);
+    movieFile = fullfile('final_movies', [movieName '_fps' num2str(round(framerate)) '.mp4']);
+    
+    ffmpegStr = sprintf('ffmpeg -y -framerate %4.2f -i %s -pix_fmt yuv420p -r %d %s', framerate, inpattern, fps, movieFile);
+    status = system(ffmpegStr);
+    
+%     %% Remove frame dir (if ffmpeg returns no errors)
+%     if ( status == 0 )
+%         rmdir(frameDir,'s');
+%     end
+end
+
+function bCompleted = awaitRenderDone(timeout, uiserver_info)
+    bCompleted=false;
+    
+    elapsed = 0;
+    chk_timer = tic();
+    while ( ~bCompleted && (timeout <= 0 || elapsed <= timeout) )
+        bCompleted = webread([uiserver_info.host '/drawComplete'], uiserver_info.opts);
+        if ( bCompleted )
+            return;
+        end
+        
+        pause(0.5);
+        elapsed = toc(chk_timer);
+    end
+end
diff --git a/+Movie/save_movie_params.m b/+Movie/save_movie_params.m
new file mode 100644
index 0000000000000000000000000000000000000000..3b6bd690a3fc7562e6b91b1c5827bf3aa6051e95
--- /dev/null
+++ b/+Movie/save_movie_params.m
@@ -0,0 +1,21 @@
+function save_movie_params(movieName, uiserver_info)
+    if ( ~exist('uiserver_info','var') )
+        web_opts = weboptions('Timeout',60);
+        uiserver_info = struct('host','http://localhost:4444', ...
+                            'opts',web_opts);
+    end
+    uiserver_info.opts.MediaType = 'application/json';
+    
+    
+    renderparams = webread([uiserver_info.host '/renderparams'], uiserver_info.opts);
+    viewparams = webread([uiserver_info.host '/view'], uiserver_info.opts);
+    
+    outparams = struct('render',{renderparams}, 'view',{viewparams});
+    
+    jsonData = Utils.CreateJSON(outparams, true);
+    
+    dataDir = 'Data';
+    fid = fopen(fullfile(dataDir, [movieName '_params.json']), 'wt');
+    fprintf(fid, '%s\n', jsonData);
+    fclose(fid);
+end
diff --git a/+Movie/set_movie_params.m b/+Movie/set_movie_params.m
new file mode 100644
index 0000000000000000000000000000000000000000..7d98b7d73ab74049b4cd5bb70b925c38c0e3e42b
--- /dev/null
+++ b/+Movie/set_movie_params.m
@@ -0,0 +1,21 @@
+function set_movie_params(movieName, uiserver_info)
+    if ( ~exist('uiserver_info','var') )
+        web_opts = weboptions('Timeout',60);
+        uiserver_info = struct('host','http://localhost:4444', ...
+                            'opts',web_opts);
+    end
+    uiserver_info.opts.MediaType = 'application/json';
+    
+    dataDir = 'Data';
+    jsonData = fileread(fullfile(dataDir, [movieName '_params.json']));
+    inParams = jsondecode(jsonData);
+    
+    if ( ~isfield(inParams.view, 'volColor') )
+        inParams.view.volColor = [0,0,0];
+    end
+    
+    inParams.view.bgColor = [0,0,0,1];
+    
+    webwrite([uiserver_info.host '/renderparams'],uiserver_info.opts, inParams.render);
+    webwrite([uiserver_info.host '/view'],uiserver_info.opts, inParams.view);
+end
diff --git a/+Segment/add_fake_sphere_seg.m b/+Segment/add_fake_sphere_seg.m
new file mode 100644
index 0000000000000000000000000000000000000000..1a3d6128736853b5fe249ad4a9015ef9871e5ab8
--- /dev/null
+++ b/+Segment/add_fake_sphere_seg.m
@@ -0,0 +1,22 @@
+function add_fake_sphere_seg(leverFile, radius)
+    conn = database(leverFile, '','', 'org.sqlite.JDBC', 'jdbc:sqlite:');
+    CONSTANTS = Read.getConstants(conn);
+    
+    imDims = Utils.SwapXY_RC(CONSTANTS.imageData.Dimensions);
+    pxSize = CONSTANTS.imageData.PixelPhysicalSize;
+    
+    [f,e,v,n] = Segment.make_fake_sphere(radius, imDims, pxSize);
+    
+    [X,Y,Z] = meshgrid(1:imDims(1),1:imDims(2),1:imDims(3));
+    coords = [X(:),Y(:),Z(:)];
+    
+    com = imDims / 2;
+    bInside = (sum((coords-com).^2, 2) <= radius^2);
+    pts = uint16(coords(bInside,:));
+    
+    segStruct = struct('time',{[1]}, 'channel',{[2]}, 'maxRadius',{[radius]}, 'area',{size(pts,1)}, ...
+                  'centroid',{imDims/2}, 'pts',{coords(bInside)}, 'segCC',{[1]}, ...
+                  'verts', {v}, 'edges',{e}, 'normals',{n}, 'faces',{f});
+    
+	Write.CreateCells_3D(conn, segStruct);
+end
diff --git a/+Segment/hipdog.m b/+Segment/hipdog.m
new file mode 100644
index 0000000000000000000000000000000000000000..8aa10222f2d66ac10bd695ad6dc5b3f7ed734ba8
--- /dev/null
+++ b/+Segment/hipdog.m
@@ -0,0 +1,6 @@
+function imDoG = hipdog(imIn, sigma)
+    s2 = sigma * sqrt(2);
+    s1 = sigma / sqrt(2);
+    
+    imDoG = HIP.Gaussian(imIn, s2,[],[]) - HIP.Gaussian(imIn, s1,[],[]);
+end
diff --git a/+Segment/leverjs_seg_dataset.m b/+Segment/leverjs_seg_dataset.m
new file mode 100644
index 0000000000000000000000000000000000000000..69015a08d0291e06afb73b154f7f7224042203de
--- /dev/null
+++ b/+Segment/leverjs_seg_dataset.m
@@ -0,0 +1,38 @@
+function leverjs_seg_dataset(leverFile, thresh)
+    conn = database(leverFile, '','', 'org.sqlite.JDBC', 'jdbc:sqlite:');
+    CONSTANTS = Read.getConstants(conn);
+    
+    maxFrame = CONSTANTS.imageData.NumberOfFrames;
+    chkDeltas = squeeze(CONSTANTS.imageData.TimeStampDelta(1,1,:));
+    
+    medDelta = median(diff(chkDeltas,1));
+    if ( medDelta < 7.0 )
+        % Cut high-framerates at 20-minutes
+        maxTime = 20*60;
+        dropFrame = find(chkDeltas >= maxTime ,1,'first');
+        if ( ~isempty(dropFrame) )
+            maxFrame = dropFrame;
+        end
+    end
+    
+    
+    prg = Utils.CmdlnProgress(maxFrame, true, ['Segmenting: ' CONSTANTS.imageData.DatasetName]);
+    for t=1:maxFrame
+        segs = Segment.segFrame(t, CONSTANTS, thresh, 0.17);
+        if ( isempty(segs) )
+            continue;
+        end
+        
+        Write.CreateCells_3D(conn, segs);
+        
+        prg.PrintProgress(t);
+    end
+    
+    prg = Utils.CmdlnProgress(maxFrame, true, ['Computing distances: ' CONSTANTS.imageData.DatasetName]);
+    for t=1:maxFrame
+        Distance.cellDistance(conn,CONSTANTS, t, [(t-2):(t-1)]);
+        
+        prg.PrintProgress(t);
+    end
+    fprintf('Finished segmenting: %s\n\n', CONSTANTS.imageData.DatasetName);
+end
diff --git a/+Segment/llsm_mask.m b/+Segment/llsm_mask.m
new file mode 100644
index 0000000000000000000000000000000000000000..286a32a2844a4279a634d365f1cc3547c02cc29b
--- /dev/null
+++ b/+Segment/llsm_mask.m
@@ -0,0 +1,10 @@
+function bwMask = llsm_mask(im)
+    %% Try to find the "parallelogram" mask of LLSM data
+    bwLLSM = any(im > 0, 4);
+    
+    % Fill holes in each slice to get the full mask
+    bwMask = false(size(bwLLSM));
+    for z=1:size(bwLLSM,3)
+        bwMask(:,:,z) = imfill(bwLLSM(:,:,z), 'holes');
+    end
+end
diff --git a/+Segment/logSeg.m b/+Segment/logSeg.m
new file mode 100644
index 0000000000000000000000000000000000000000..67f7100124acd8ab300daa95d03888b368830ada
--- /dev/null
+++ b/+Segment/logSeg.m
@@ -0,0 +1,30 @@
+function [bwIm,imNg] = logSeg(im, imD, logThresh, vesicle_rad_um)
+    %%
+%     logThresh = 0.003;
+    scale_factor = 1.0;
+    vesicle_rad_px = vesicle_rad_um ./ imD.PixelPhysicalSize;
+
+    imCalcium = single(mat2gray(im));
+%     szFilter = round(size(imCalcium)/3);
+%     imLP = HIP.Gaussian(imCalcium, szFilter, 1, []);
+%     imHP = imCalcium - imLP;
+%     % radKern = HIP.MakeEllipsoidMask([2 2 1]);
+%     % imx = HIP.MedianFilter(imHP, radKern, 1, []);
+%     imx = imHP;
+
+    %%
+%     imDog = hipdog(imCalcium, 2*scale_factor*vesicle_rad_px.*[1 1 1]);
+    imDog = HIP.LoG(imCalcium, 2*scale_factor*vesicle_rad_px.*[1 1 1], []);
+
+    bwMask = false(size(imDog));
+    bwMask(imDog<0) = true;
+
+    imNg = zeros(size(imDog));
+    imNg(bwMask) = -imDog(bwMask);
+    
+    % TODO: Figure out how to pick a better threshold
+    bw = imNg >= logThresh;
+    
+    se = HIP.MakeEllipsoidMask([2,2,1]);
+    bwIm = HIP.Opener(bw, se, [], []);
+end
diff --git a/+Segment/makePoly.m b/+Segment/makePoly.m
new file mode 100644
index 0000000000000000000000000000000000000000..5bc0d70580e7ad92b37922ed269ac2a73da69dc4
--- /dev/null
+++ b/+Segment/makePoly.m
@@ -0,0 +1,64 @@
+function [faces,verts,normals,edges] = makePoly(pixels_xyz, imSize_rc, maxScale)
+    faces = [];
+    verts = [];
+    normals = [];
+    edges = [];
+    
+    if ( ~exist('maxScale','var') )
+        maxScale = 8;
+    end
+    
+    PADDING = 2 * maxScale;
+    
+    scale_rc = max(imSize_rc) ./ imSize_rc / maxScale;
+    scale_rc = max(min(scale_rc, 0.5), 0.01);
+
+    startCoords_rcz = Utils.SwapXY_RC(min(round(pixels_xyz),[],1));
+    endCoords_rcz = Utils.SwapXY_RC(max(round(pixels_xyz),[],1));
+
+    startPadded = startCoords_rcz-PADDING;
+    endPadded = endCoords_rcz+PADDING;
+
+    roiIm = zeros(ceil(endPadded-startPadded+1));
+
+    paddedPixelList_rcz = Utils.SwapXY_RC(pixels_xyz)-startPadded+1;
+    indList = Utils.CoordToInd(size(roiIm),paddedPixelList_rcz);
+    roiIm(indList) = 1;
+    
+
+    if ( all(scale_rc == 1) )
+        smImSize = size(roiIm);
+        imR = roiIm;
+    else
+        smImSize = round(scale_rc .* size(roiIm));
+        imR = imresize3(roiIm, smImSize);
+    end
+    
+    [f,v] = isosurface(imR, graythresh(imR));
+    if ( size(v,1) < 10 )
+        smImSize = round(2 * scale_rc .* size(roiIm));
+        imR = imresize3(roiIm, smImSize);
+        [f,v] = isosurface(imR, graythresh(imR));
+    end
+    
+    if ( isempty(v) )
+        return;
+    end
+    
+    % rescale verts back to 
+    unscale_rc = (size(roiIm) ./ size(imR));
+    % put unscale on x,y instead of r,c
+    unscale_xy = Utils.SwapXY_RC(unscale_rc);
+    
+    verts = (v-0.5) .* unscale_xy + Utils.SwapXY_RC(startPadded);
+    normals = D3d.Polygon.CalcNorms(verts, f);
+    
+    % make sure face/edge indices are 0-based
+    faces = f-1;
+    edges = makeEdges(f)-1;
+end
+
+function edges = makeEdges(faces)
+    fullEdges = [faces(:,[1 2]); faces(:,[2 3]); faces(:,[3 1])];
+    edges = unique(sort(fullEdges, 2), 'rows','stable');
+end
diff --git a/+Segment/makeSegStruct.m b/+Segment/makeSegStruct.m
new file mode 100644
index 0000000000000000000000000000000000000000..74f994a2f5c8ea0cec55b812a917f1ea47edbf6a
--- /dev/null
+++ b/+Segment/makeSegStruct.m
@@ -0,0 +1,46 @@
+function segs = makeSegStruct(time,chan, rp, imSize, isoScale, orgL)
+    if ( ~exist('orgL','var') )
+        orgL = [];
+    end
+
+    % Pre-allocate segs structure for frame
+    times = num2cell(repmat(time, length(rp),1));
+    chans = num2cell(repmat(chan, length(rp),1));
+    segs = struct('time',times, 'channel',chans, 'maxRadius',{[]}, 'area',{[]}, ...
+                  'centroid',{[]}, 'pts',{[]}, 'segCC',{[]}, ...
+                  'verts', {[]}, 'edges',{[]}, 'normals',{[]}, 'faces',{[]});
+    
+    for i=1:length(rp)
+        ns = segs(i);
+        
+        ns.area = size(rp(i).PixelList,1);
+        ns.centroid = rp(i).Centroid;
+        ns.pts = uint16(rp(i).PixelList);
+        
+        if ( isempty(orgL) )
+            ns.segCC = i;
+        else
+            ns.segCC = findOrgCC(rp(i).PixelIdxList, orgL);
+        end
+        
+        [ns.faces,ns.verts,ns.normals,ns.edges] = Segment.makePoly(rp(i).PixelList, imSize, isoScale);
+        if ( isempty(ns.faces) )
+            continue;
+        end
+        
+        ns.maxRadius = sqrt(max(sum((ns.verts - ns.centroid).^2, 2)));
+        
+        segs(i) = ns;
+    end
+    
+    %% Drop empty segmentations (e.g. failed isosurfacing)
+    bValidIso = arrayfun(@(x)(~isempty(x.faces)), segs);
+    segs = segs(bValidIso);
+end
+
+function orgCC = findOrgCC(idxList, orgL)
+        orgCC = unique(orgL(idxList));
+        if ( length(orgCC) > 1 )
+            orgCC = orgCC(find(orgCC,1,'first'));
+        end
+end
diff --git a/+Segment/make_fake_sphere.m b/+Segment/make_fake_sphere.m
new file mode 100644
index 0000000000000000000000000000000000000000..569641235c0af86957a0fe83571b05df428d3f52
--- /dev/null
+++ b/+Segment/make_fake_sphere.m
@@ -0,0 +1,141 @@
+function [f,e,v,n] = make_fake_sphere(radius, imDims, pxSize)
+    [f,e,v,n] = make_sphere(radius);
+    
+    scl = repmat(min(pxSize) ./ pxSize, size(v,1),1);
+    v = v .* scl;
+    
+    n = calcNormals(v,f);
+%     n = n ./ scl;
+%     n = n ./ sqrt(sum(n.^2,2));
+
+%     figure;hold on;
+%     trisurf(f, v(:,1),v(:,2),v(:,3), 'EdgeColor','none');
+%     
+%     ppX = [v(e(:,1),1) v(e(:,2),1)].';
+%     ppY = [v(e(:,1),2) v(e(:,2),2)].';
+%     ppZ = [v(e(:,1),3) v(e(:,2),3)].';
+%     plot3(ppX, ppY, ppZ, '-k');
+%     
+%     npX = [v(:,1) v(:,1)+0.2*n(:,1)].';
+%     npY = [v(:,2) v(:,2)+0.2*n(:,2)].';
+%     npZ = [v(:,3) v(:,3)+0.2*n(:,3)].';
+%     plot3(npX,npY,npZ, '-r');
+%     
+%     v = v + (imDims ./ 2);
+end
+
+function [f,e,v,n] = make_sphere(r)
+    nsegs = 20;
+    theta = linspace(-180,180, 2*nsegs);
+    phi = linspace(-90,90, nsegs);
+    
+    [ttheta,tphi] = meshgrid(theta(1:end-1), phi(2:end-1));
+    
+    uy = sind(tphi);
+    ux = cosd(tphi) .* cosd(ttheta);
+    uz = cosd(tphi) .* sind(ttheta);
+    
+    v = r * [0,-1,0;
+        ux(:),uy(:),uz(:);
+        0,1,0];
+    
+    n = v ./ sqrt(sum(v.^2, 2));
+    
+    f = [];
+    e = [];
+    
+    vq_offset = 1;
+    %% Make traingulated quad faces (and quad edges)
+    for i=1:size(uy,1)-1
+        for j=1:size(uy,2)
+            jnxt = mod(j, size(uy,2)) + 1;
+            
+            bl = sub2ind(size(uy), i,j) + vq_offset;
+            br = sub2ind(size(uy), i,jnxt) + vq_offset;
+            tl = sub2ind(size(uy), i+1,j) + vq_offset;
+            tr = sub2ind(size(uy), i+1,jnxt) + vq_offset;
+            
+            qt1 = [bl, br, tl];
+            qt2 = [br, tr, tl];
+            
+            qel = [tl, bl];
+            qeb = [bl, br];
+            
+            f = [f; qt1;qt2];
+            e = [e; qel;qeb];
+        end
+    end
+    
+    %% Make bottom triangle cap (and edges)
+    bcf = [];
+    bce = [];
+    for j=1:size(uy,2)
+        jnxt = mod(j, size(uy,2)) + 1;
+        
+        tl = sub2ind(size(uy), 1,j) + vq_offset;
+        tr = sub2ind(size(uy), 1,jnxt) + vq_offset;
+        
+        bcf = [bcf; 1,tr,tl];
+        bce = [bce; tl,1];
+    end
+    
+    %% Make top triangle cap (and edges)
+    tcf = [];
+    tce = [];
+    for j=1:size(uy,2)
+        jnxt = mod(j, size(uy,2)) + 1;
+        
+        bl = sub2ind(size(uy), size(uy,1),j) + vq_offset;
+        br = sub2ind(size(uy), size(uy,1),jnxt) + vq_offset;
+        
+        tel = [size(v,1), bl];
+        teb = [bl, br];
+        
+        tcf = [tcf; size(v,1),bl,br];
+        tce = [tce; tel;teb];
+    end
+    
+    f = [bcf;f;tcf];
+    e = [bce;e;tce];
+    
+%     figure;hold on;
+%     trisurf(f, v(:,1),v(:,2),v(:,3), 'EdgeColor','none');
+%     
+%     ppX = [v(e(:,1),1) v(e(:,2),1)].';
+%     ppY = [v(e(:,1),2) v(e(:,2),2)].';
+%     ppZ = [v(e(:,1),3) v(e(:,2),3)].';
+%     plot3(ppX, ppY, ppZ, '-k');
+%     
+%     npX = [v(:,1) v(:,1)+0.2*n(:,1)].';
+%     npY = [v(:,2) v(:,2)+0.2*n(:,2)].';
+%     npZ = [v(:,3) v(:,3)+0.2*n(:,3)].';
+%     plot3(npX,npY,npZ, '-r');
+end
+
+function vertNormals = calcNormals(vertList, faceList)
+    vertNormals = zeros(size(vertList,1),3);
+    for i=1:size(faceList,1)
+        face = faceList(i,:);
+        edges = [vertList(face(2),:)-vertList(face(1),:);
+                 vertList(face(3),:)-vertList(face(1),:);
+                 vertList(face(3),:)-vertList(face(2),:)];
+        
+        edgeLen = sqrt(sum(edges.^2,2));
+        edges = edges ./ repmat(edgeLen,1,3);
+        
+        faceDir = [dot(edges(1,:),edges(2,:));
+                   dot(edges(3,:),-edges(1,:));
+                   dot(edges(2,:),edges(3,:))];
+        
+        faceAngles = acos(faceDir);
+        
+        faceVec = cross(edges(1,:), edges(2,:));
+        faceNorm = faceVec / norm(faceVec);
+        
+        for k=1:3
+            vertNormals(face(k),:) = vertNormals(face(k),:) + faceAngles(k)*faceNorm;
+        end
+    end
+    vertLengths = sqrt(sum(vertNormals.^2,2));
+    vertNormals = -(vertNormals ./ repmat(vertLengths,1,3));
+end
diff --git a/+Segment/segFrame.m b/+Segment/segFrame.m
new file mode 100644
index 0000000000000000000000000000000000000000..42d98e062eb6c67c715edddcaf5a3a45997ef4e0
--- /dev/null
+++ b/+Segment/segFrame.m
@@ -0,0 +1,86 @@
+function [segs,imNg] = segFrame(t, CONSTANTS, thresh, rad_um)
+    [im,imD] = MicroscopeData.Reader('imageData',CONSTANTS.imageData, 'timeRange',[t,t]);
+    
+    validVolume = 20;
+    
+    fmSegs = [];
+%     %% TODO: Use FM 4-64 to try and identify ectoderm as a whole
+%     imFM = mat2gray(im(:,:,:,1));
+%     thEct = 0.8*graythresh(imFM);
+%     
+%     bwFM = (imFM >= thEct);
+%     
+%     closeKern = HIP.MakeEllipsoidMask([3,3,1]);
+%     bwEct = HIP.Closure(bwFM, closeKern, [],[]);
+%     
+%     rp = regionprops(bwEct, 'Area', 'PixelIdxList', 'PixelList', 'Centroid');
+%     [mxArea,maxIdx] = max([rp.Area]);
+%     
+%     if (mxArea < 9000)
+%         warning(['Couldn''t find ectoderm in frame: ' t]);
+%     else
+%         fmSegs = makeSegStruct(t, 1, rp, size(bwEct), 1);
+%     end
+    
+    
+    %% Guess spicule brightness from graythresh
+    % TODO: Try to autoscale threshold somehow?
+    
+    %% TODO: Deal with bright spicule first
+    [bw,imNg] = Segment.logSeg(im(:,:,:,2), imD, thresh, rad_um);
+    
+    %% Only keep large CCs
+    rp = regionprops(bw, 'Area', 'PixelIdxList');
+    bValid = arrayfun(@(x)(x.Area > validVolume), rp);
+    
+    idxList = vertcat(rp(bValid).PixelIdxList);
+    
+    bwValid = false(size(bw));
+    bwValid(idxList) = true;
+
+    %% Get initial labeling
+    orgL = bwlabeln(bwValid);
+    
+    %% Apply a small erosion to try and separate nearby components
+    axScale = min(CONSTANTS.imageData.PixelPhysicalSize) ./ CONSTANTS.imageData.PixelPhysicalSize;
+    
+    erodeKern = HIP.MakeEllipsoidMask([3,3,1]);
+    bwEr = HIP.MinFilter(bwValid, erodeKern, [],[]);
+    
+    %% Add back disappeared labels
+    allL = unique(orgL(orgL>0));
+    keptL = orgL(bwEr);
+    
+    missingL = setdiff(allL, keptL);
+    bAddBack = ismember(orgL(:), missingL);
+    
+    bwEr(bAddBack) = true;
+    
+    %% Use distance tfm to backfill
+    L = bwlabeln(bwEr);
+    
+    [~, idxFg] = bwdist(bwEr);
+    L(bwValid) = L(idxFg(bwValid));
+    
+    
+%     %% Use Watershed to backfill
+%     D = bwdist(bwEr);
+%     D(~bw) = -Inf;
+%     
+%     L = watershed(D);
+%     bwReassign = (bw & (L <= 1));
+%     
+%     % Assign the border/bg pixels that watershed drops
+%     [~,idxFg] = bwdist(L>1);
+%     L(bwReassign) = idxFg(bwReassign);
+%     
+%     % Reorder the labels
+%     L(L<=1) = 1;
+%     L = L-1;
+    
+    %% Build seg structure
+    rp = regionprops(L, 'PixelIdxList', 'PixelList', 'Centroid');
+    caSegs = Segment.makeSegStruct(t, 2, rp, size(orgL), 1, orgL);
+    
+    segs = [fmSegs; caSegs];
+end
diff --git a/+Stats/abbreviateName.m b/+Stats/abbreviateName.m
new file mode 100644
index 0000000000000000000000000000000000000000..e9955a4beaaadc66723bd3f3c986cc8524876f76
--- /dev/null
+++ b/+Stats/abbreviateName.m
@@ -0,0 +1,22 @@
+function abrv = abbreviateName(name)
+    n_date = name(1:5);
+    type = regexpi(name, '(?:DMSO)|(?:Axtinib)|(?:Axitinib)|(?:BB94)', 'match','once');
+    tlid = regexpi(name, 'TimeLapse(\d+)', 'tokens','once');
+    posid = regexpi(name, 'Pos(\d+)', 'tokens','once');
+    
+    % Handle spelling difference
+    type = strrep(type, 'Axitinib','Axtinib');
+    
+    wash = regexpi(name, '_wash_', 'match','once');
+    if ( ~isempty(wash) )
+        type = [type '_w'];
+    end
+    
+    abrv = [type '_' n_date];
+    if ( ~isempty(tlid) )
+        abrv = [abrv '_T' tlid{:}];
+    end
+    if ( ~isempty(posid) )
+        abrv = [abrv '_P' posid{:}];
+    end
+end
diff --git a/+Stats/check_bb94_stats.m b/+Stats/check_bb94_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..ccc6c6adc5528ff6a58c0db771ffda5d5989b320
--- /dev/null
+++ b/+Stats/check_bb94_stats.m
@@ -0,0 +1,12 @@
+%%
+Stats.export_bb94_stats
+
+%% Create tables for easier data access
+size_tbl = readtable('ecto_size_stats_bb94.csv');
+
+%% Create csv with comparisons
+fid = fopen('mmpl_sigs.csv', 'wt');
+
+%% Write size comparison
+sum_tbl = Stats.write_summary(fid, size_tbl, 'size', 'Vesicle Size', {'Control';'Metalloproteinase Inhibition'});
+Stats.write_compare(fid, size_tbl, sum_tbl, 'size', 'Vesicle Size Comparison', {'Control';'Metalloproteinase Inhibition'});
diff --git a/+Stats/check_ecto_stats.m b/+Stats/check_ecto_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..3124e56da361ffdb8b67ac73ff108a4f57d6c255
--- /dev/null
+++ b/+Stats/check_ecto_stats.m
@@ -0,0 +1,57 @@
+%%
+Stats.export_ecto_stats;
+Stats.export_spicule_stats;
+
+%% Create tables for easier data access
+size_tbl = readtable('ecto_size_stats_axtinib.csv');
+vesicle_tbl = readtable('ecto_track_stats_axtinib.csv');
+spc_tbl = readtable('spicule_stats.csv');
+
+%% Create csv with comparisons
+fid = fopen('ecto_sigs.csv', 'wt');
+
+%% Write size comparison
+sum_tbl = Stats.write_summary(fid, size_tbl, 'size', 'Vesicle Size');
+Stats.write_compare(fid, size_tbl, sum_tbl, 'size', 'Vesicle Size Comparison');
+
+%% Speed comparison
+sum_tbl = Stats.write_summary(fid, vesicle_tbl, 'avg_speed', 'Vesicle Speed');
+Stats.write_compare(fid, vesicle_tbl, sum_tbl, 'avg_speed', 'Vesicle Speed Comparison');
+
+%% Directionality comparison
+sum_tbl = Stats.write_summary(fid, vesicle_tbl, 'dir_index', 'Vesicle Directionality');
+Stats.write_compare(fid, vesicle_tbl, sum_tbl, 'dir_index', 'Vesicle Directionality Comparison');
+
+%% Diffusion comparison
+sum_tbl = Stats.write_summary(fid, vesicle_tbl, 'msd_D', 'Vesicle Diffusion');
+Stats.write_compare(fid, vesicle_tbl, sum_tbl, 'msd_D', 'Vesicle Diffusion Comparison');
+
+%% Spicule motion stats
+sum_tbl = Stats.write_summary_spicule(fid, spc_tbl, 'abs_vel', 'Instantaneous Speed');
+[pKW_s,pM_s] = Stats.write_compare_spicule(fid, spc_tbl, sum_tbl, 'abs_vel', 'Instantaneous Speed Comparison');
+
+%% Spicule vel stats
+Stats.write_summary_spicule(fid, spc_tbl, 'inst_vel', 'Instantaneous Velocity');
+[pKW_v,pM_v] = Stats.write_compare_spicule(fid, spc_tbl, sum_tbl, 'inst_vel', 'Velocity Toward Spicul Comparison');
+
+%% Plot pM as a "matrix"
+cmaplh = hot(10);
+cmap = flip(cmaplh(1:9,:),1);
+clims = log10([min(pM_s(:)) max(pM_s(:))]);
+
+figure;imagesc(log10(pM_s),[-9,0]);colormap(cmap);
+hold on;
+for i=1:10
+    plot([i+0.5,i+0.5],[0.5,10.5], '-k');
+    plot([0.5,10.5],[i+0.5,i+0.5], '-k');
+end
+labels = flip(['1', arrayfun(@(x)(['10^{' num2str(x) '}']), (-1:-1:-8), 'UniformOutput',false)]);
+colorbar('Ticks',flip(0:-1:-8), 'TickLabels',labels)
+set(gca,'XAxisLocation','top')
+axis tight
+xlim([0.5,10.5]);
+ylim([0.5,10.5]);
+
+
+%%
+fclose(fid);
diff --git a/+Stats/collate_all_tracks.m b/+Stats/collate_all_tracks.m
new file mode 100644
index 0000000000000000000000000000000000000000..26f8e74a1b1935e53c8876aa25167d931208eb94
--- /dev/null
+++ b/+Stats/collate_all_tracks.m
@@ -0,0 +1,36 @@
+ROOT = 'E:\Smadar\LeverJS';
+
+partial_prefix = 'dataset_collate_tracks_p';
+
+flist = dir(fullfile(ROOT,'*.LEVER'));
+parfor i=1:length(flist)
+    track_data = Stats.collate_tracks(ROOT, flist(i).name);
+    if ( isempty(track_data) )
+        continue;
+    end
+    
+    Stats.parsave(partial_prefix, i, {'track_data'}, {track_data});
+end
+
+% flist = dir(fullfile(ROOT,'*.LEVER'));
+% for i=1:length(flist)
+%     track_data = Stats.collate_tracks(ROOT, flist(i).name);
+%     if ( isempty(track_data) )
+%         continue;
+%     end
+%     
+%     Stats.parsave(partial_prefix, i, {'track_data'}, {track_data});
+% end
+
+date_suffix = ['-' datestr(now(),'yyyy-mm-dd')];
+
+track_data = [];
+matList = dir([partial_prefix '*.mat']);
+for i=1:length(matList)
+    s = load(matList(i).name);
+    
+    track_data = [track_data; s.track_data];
+    
+    outname = ['dataset_collate_tracks' date_suffix '.mat'];
+    save(outname, 'track_data');
+end
diff --git a/+Stats/collate_tracks.m b/+Stats/collate_tracks.m
new file mode 100644
index 0000000000000000000000000000000000000000..eba30b8e69b9d478a0d55e19290cc46c1b7f63cf
--- /dev/null
+++ b/+Stats/collate_tracks.m
@@ -0,0 +1,183 @@
+function track_data = collate_tracks(ROOT, filename)
+    leverFile = fullfile(ROOT,filename);
+    
+    track_data = [];
+    
+    %% Load constants
+    conn = database(leverFile, '','', 'org.sqlite.JDBC', 'jdbc:sqlite:');
+    CONSTANTS = Read.getConstants(conn);
+    
+    %% Setup time and pixel resolution info
+    cutoff = 60*15;
+    bKeep = CONSTANTS.imageData.TimeStampDelta(1,1,:) <= cutoff;
+    
+    pxSize = CONSTANTS.imageData.PixelPhysicalSize;
+    pxVol = prod(pxSize);
+    
+    tsmp = squeeze(CONSTANTS.imageData.TimeStampDelta(1,1,:));
+    
+    %% Set number of frames to consider
+    keeplen = nnz(bKeep);
+    
+    %% Create abbreviation for dataset
+    datasetName = CONSTANTS.imageData.DatasetName;
+    abrv = Stats.abbreviateName(datasetName);
+    
+    %% Estimate parallelogram mask for first-frame image
+    imagePath = CONSTANTS.imageData.imageDir;
+    im = MicroscopeData.Reader(imagePath, 'timeRange',[1,1]);
+    
+    parallelMask = Segment.llsm_mask(im);
+    % HACK: Drop messy top/bottom slices in mask
+    parallelMask(:,:,1) = false;
+    parallelMask(:,:,end) = false;
+    
+    %% Get ecto/endo regions
+    dataPath = 'Data';
+    maskPath = fullfile(dataPath, 'ectoMasks');
+    
+    ectoPath = fullfile(maskPath, [datasetName '_ectoregion.mat']);
+    endoPath = fullfile(maskPath, [datasetName '_endoregion.mat']);
+    
+    bHasEcto = exist(ectoPath, 'file');
+    bHasEndo = exist(endoPath, 'file');
+    
+    endoInfo = [];
+    bwEndo = false(size(parallelMask));
+    
+    %%%%%%%%%%%%%%%%%%%%
+    %% Exit if there's not ectoderm region info
+    size_stats = [];
+    if ( ~bHasEcto )
+        return;
+    end
+    
+    %% Skip if movie is >= 7fps
+    medTsmp = median(diff(tsmp));
+    if ( medTsmp >= 7.0 )
+        return;
+    end
+    
+    %% Set frame cutoff should be no more than 5 minutes
+    cutoff = 60 * 5;
+    bKeep = CONSTANTS.imageData.TimeStampDelta(1,1,:) <= cutoff;
+    keeplen = nnz(bKeep);
+    
+    %% Load edge/track info
+    trackInfoPath = fullfile(dataPath, [datasetName '_trackinfo.mat']);
+    if ( ~exist(trackInfoPath, 'file') )
+        return;
+    end
+    
+    load(fullfile(dataPath, [datasetName '_edgeinfo.mat']));
+    load(trackInfoPath);
+    
+    %% Load ectoderm and endoderm region info
+    ectoInfo = load(ectoPath);
+    bwEcto = (parallelMask & ectoInfo.regionInfo.bwMask(:,:,:,1));
+    
+    if ( bHasEndo )
+        endoInfo = load(endoPath);
+        bwEndo = (parallelMask & endoInfo.regionInfo.bwMask(:,:,:,1));
+    end
+    
+    bwSkel = (parallelMask & ~bwEcto & ~bwEndo);
+    
+    
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+    %% Read per-frame vesicle info
+    bEcto = false(0,1);
+    bSkel = false(0,1);
+    bEndo = false(0,1);
+    
+    tids = zeros(0,1);
+    coms = zeros(0,3);
+    ctimes = zeros(0,1);
+    csizes = zeros(0,1);
+    cframes = zeros(0,1);
+    
+    imSize = [size(im,1), size(im,2), size(im,3)];
+    for t=1:keeplen
+        Cells = Read.getCells3DTime(conn,t);
+        if ( isempty(Cells) )
+            return;
+        end
+    
+        cellsIdx = cellfun(@(x)(Utils.CoordToInd(imSize, double(Utils.SwapXY_RC(x)))), {Cells.pts}.', 'UniformOutput',false);
+        cids = vertcat(Cells.cellID);
+    
+        %% Assign vesicles to regions by max overlap
+        nEcto = cellfun(@(x)(nnz(bwEcto(x))), cellsIdx);
+        nSkel = cellfun(@(x)(nnz(bwSkel(x))), cellsIdx);
+        nEndo = cellfun(@(x)(nnz(bwEndo(x))), cellsIdx);
+
+        bEcto(cids) = (nEcto >= nSkel & nEcto >= nEndo);
+        bSkel(cids) = (nSkel >= nEndo & ~bEcto(cids));
+        bEndo(cids) = (~bEcto(cids) & ~bSkel(cids));
+        
+        tids(cids,:) = vertcat(Cells.trackID);
+        coms(cids,:) = horzcat(Cells.centroid).' .* pxSize;
+        csizes(cids) = vertcat(Cells.area) .* pxVol;
+        ctimes(cids) = tsmp(vertcat(Cells.time));
+        cframes(cids) = vertcat(Cells.time);
+    end
+    
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+    %% Tracking info
+    
+    %% Get tracks to keep
+    minTrackLen = 7;
+    
+    tlens = cellfun(@(x)(length(x)), trackInfo);
+    bKeep = (tlens >= minTrackLen);
+    
+    lastId = length(csizes);
+    trackInfo = trackInfo(bKeep);
+    for i=1:length(trackInfo)
+        bValid = (trackInfo{i} <= lastId);
+        trackInfo{i} = trackInfo{i}(bValid);
+    end
+    
+    bValidInfo = cellfun(@(x)(length(x) >= 5), trackInfo);
+    trackInfo = trackInfo(bValidInfo);
+    
+    
+    %% Assign tracks to region by majority vote
+    tnEcto = cellfun(@(x)(sum(bEcto(x))), trackInfo);
+    tnSkel = cellfun(@(x)(sum(bSkel(x))), trackInfo);
+    tnEndo = cellfun(@(x)(sum(bEndo(x))), trackInfo);
+    
+    btEcto = (tnEcto >= tnSkel & tnEcto >= tnEndo);
+    btSkel = (tnSkel >= tnEndo & ~btEcto);
+    btEndo = (~btEcto & ~btSkel);
+    
+    %%
+    %% Load per-track data
+    typeCell = regexpi(abrv,'(\w*?)_','tokens','once');
+    type = typeCell{1};
+    
+    regions = cell(length(trackInfo),1);
+    regions(btEcto) = {'ecto'};
+    regions(btSkel) = {'skel'};
+    regions(btEndo) = {'endo'};
+    
+    track_data = [];
+    for i=1:length(trackInfo)
+        %% Compute per-track info
+        cids = trackInfo{i};
+        
+        tid = tids(cids(1));
+        ttime = ctimes(cids);
+        tsize = csizes(cids);
+        tpos = coms(cids,:);
+        
+        tdt = diff(ttime);
+        tdx = diff(tpos,1,1);
+        tmsd = sum(tdx.^2, 2);
+        
+        nts = struct('name',{datasetName}, 'abrv',{abrv}, 'type',{type}, 'region',{regions{i}}, 'srate',{medTsmp}, 'tid',{tid}, 'times',{ttime}, 'pos',{tpos}, 'sizes',{tsize}, 'dt',{tdt}, 'dx',{tdx}, 'msd',{tmsd});
+        track_data = [track_data; nts];
+    end
+    
+%     track_data = track_data(btEcto | btSkel);
+end
diff --git a/+Stats/export_bb94_stats.m b/+Stats/export_bb94_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..8f617893c4cb1093b6556a02c9d6f5f63a21d310
--- /dev/null
+++ b/+Stats/export_bb94_stats.m
@@ -0,0 +1,154 @@
+%%
+dmso_order = {'23-06-2016_TimeLapse1_DMSO_20hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse1_Pos2_DMSO_18hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse1_Pos1_DMSO_18hrs_Calcein_FM464';
+            '23-06-2016_TimeLapse2_DMSO_20hrs_Calcein_AzepRH';
+            '28-06-2016_TimeLapse2_DMSO_22hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse1_DMSO_24hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse2_DMSO_24hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse3_DMSO_20hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos1_DMSO_25hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos3_DMSO_25hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos4_DMSO_25hrs_Calcein_FM464';
+            '24-06-2016_Timelapse4_DMSO_24hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse3_DMSO_21hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse4_DMSO_21hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse5_DMSO_23hrs_Calcein_FM464'};
+
+axt_order = {'01-07-2016_TimeLapse1_Pos0_Axtinib_150_18hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse1_Pos1_Axtinib_150_18hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse2_Pos2_Axtinib_150_19hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse2_Pos3_Axtinib_150_19hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse1_Axtinib_150_19hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse2_Axtinib_150_20hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse3_Axtinib_150_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos0_Axtinib_150_19hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos1_Axtinib_150_19hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos2_Axtinib_150_19hrs_Calcein_FM464';
+            '23-06-2016_Timelapse_3_Axitinib_36hr_Calcein_FM464'};
+
+bb94_order = {'29-06-2016_TimeLapse1_Pos0_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse1_Pos1_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse1_Pos2_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos0_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos1_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos2_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos0_BB94_23hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos2_BB94_23hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos3_BB94_23hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos0_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos1_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos2_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos3_BB94_21hrs_Calcein_FM464'};
+
+all_order = [dmso_order; axt_order; bb94_order];
+
+flist = dir('dataset_ecto_stats-*.mat');
+fnames = {flist.name}.';
+
+load(fnames{end});
+
+%% Sort and only keep matched tracks for now
+matchCells = arrayfun(@(x)(find(strcmpi(all_order,x.name))), ecto_size_stats, 'UniformOutput',false);
+matchTracks = arrayfun(@(x)(find(strcmpi(all_order,x.name))), ecto_track_stats, 'UniformOutput',false);
+
+bMatchedS = cellfun(@(x)(~isempty(x)), matchCells);
+bMatchedT = cellfun(@(x)(~isempty(x)), matchTracks);
+
+chk_size_stats = ecto_size_stats(bMatchedS);
+chk_track_stats = ecto_track_stats(bMatchedT);
+
+[~,invMatch] = sort(vertcat(matchCells{bMatchedS}));
+chk_size_stats = chk_size_stats(invMatch);
+
+[~,invMatch] = sort(vertcat(matchTracks{bMatchedT}));
+chk_track_stats = chk_track_stats(invMatch);
+
+%% Drop empty data
+bValidEctoS = arrayfun(@(x)(~isempty(x.ecto.vsizes)), chk_size_stats);
+bValidSkelS = arrayfun(@(x)(~isempty(x.skel.vsizes)), chk_size_stats);
+
+bValidEctoT = arrayfun(@(x)(~isempty(x.ecto.mean_sizes)), chk_track_stats);
+bValidSkelT = arrayfun(@(x)(~isempty(x.skel.mean_sizes)), chk_track_stats);
+
+bError = (bValidEctoS & ~bValidSkelS);
+err_names = arrayfun(@(x)(x.abrv), chk_size_stats(bError), 'UniformOutput',false);
+if ( any(bError) )
+    fprintf('WARNING: Invalid data detected: No skeletogenic segmentations: (%s)\n', strjoin(err_names,','));
+end
+
+chk_size_stats = chk_size_stats(bValidEctoS);
+
+bError = (bValidEctoT & ~bValidSkelT);
+err_names = arrayfun(@(x)(x.abrv), chk_size_stats(bError), 'UniformOutput',false);
+if ( any(bError) )
+    fprintf('WARNING: Invalid data detected: No skeletogenic segmentations: (%s)\n', strjoin(err_names,','));
+end
+
+chk_track_stats = chk_track_stats(bValidEctoT);
+
+%%
+exp_types = arrayfun(@(x)(regexpi(x.abrv,'(\w*?)_','tokens','once')), chk_size_stats, 'UniformOutput',false);
+exp_types = vertcat(exp_types{:});
+
+ecto_size_info = vertcat(chk_size_stats.ecto);
+skel_size_info = vertcat(chk_size_stats.skel);
+endo_size_info = vertcat(chk_size_stats.endo);
+
+exp_types_track = arrayfun(@(x)(regexpi(x.abrv,'(\w*?)_','tokens','once')), chk_track_stats, 'UniformOutput',false);
+exp_types_track = vertcat(exp_types_track{:});
+
+ecto_track_info = vertcat(chk_track_stats.ecto);
+skel_track_info = vertcat(chk_track_stats.skel);
+endo_track_info = vertcat(chk_track_stats.endo);
+
+%%
+outFile = 'ecto_size_stats_bb94.csv';
+fid = fopen(outFile, 'wt');
+
+% heading = {'experiment','exp_short','type','vid',};
+heading = {'experiment','exp_short','type','region','vid','size'};
+
+hstr = strjoin(heading,',');
+fprintf(fid, '%s\n', hstr);
+
+for i=1:length(chk_size_stats)
+    exp_name = chk_size_stats(i).name;
+    exp_short = chk_size_stats(i).abrv;
+    
+    type_cell = regexpi(chk_size_stats(i).abrv,'(\w*?)_','tokens','once');
+    exp_type = type_cell{1};
+    
+    if ( strcmpi(exp_type,'Axtinib') )
+        continue;
+    end
+    
+    exp_type = replace(exp_type, 'DMSO','Control');
+    exp_type = replace(exp_type, 'Axtinib','VEGFR Inhibition');
+    exp_type = replace(exp_type, 'BB94','Metalloproteinase Inhibition');
+    
+    vid = 1;
+    for j=1:length(chk_size_stats(i).skel.vsizes)
+        fprintf(fid, '%s,%s,%s,%s,', exp_name, exp_short, exp_type, 'Mesoderm');
+        fprintf(fid, '%d,', vid);
+        
+        fprintf(fid, '%f', chk_size_stats(i).skel.vsizes(j));
+        
+        fprintf(fid, '\n');
+        
+        vid = vid + 1;
+    end
+    
+    for j=1:length(chk_size_stats(i).ecto.vsizes)
+        fprintf(fid, '%s,%s,%s,%s,', exp_name, exp_short, exp_type, 'Ectoderm');
+        fprintf(fid, '%d,', vid);
+        
+        fprintf(fid, '%f', chk_size_stats(i).ecto.vsizes(j));
+        
+        fprintf(fid, '\n');
+        
+        vid = vid + 1;
+    end
+end
+
+fclose(fid);
diff --git a/+Stats/export_ecto_stats.m b/+Stats/export_ecto_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..eab5347bca2b4d87ed189c3853ffdf20e041e22d
--- /dev/null
+++ b/+Stats/export_ecto_stats.m
@@ -0,0 +1,235 @@
+%%
+dmso_order = {'23-06-2016_TimeLapse1_DMSO_20hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse1_Pos2_DMSO_18hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse1_Pos1_DMSO_18hrs_Calcein_FM464';
+            '23-06-2016_TimeLapse2_DMSO_20hrs_Calcein_AzepRH';
+            '28-06-2016_TimeLapse2_DMSO_22hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse1_DMSO_24hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse2_DMSO_24hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse3_DMSO_20hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos1_DMSO_25hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos3_DMSO_25hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos4_DMSO_25hrs_Calcein_FM464';
+            '24-06-2016_Timelapse4_DMSO_24hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse3_DMSO_21hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse4_DMSO_21hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse5_DMSO_23hrs_Calcein_FM464'};
+
+axt_order = {'01-07-2016_TimeLapse1_Pos0_Axtinib_150_18hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse1_Pos1_Axtinib_150_18hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse2_Pos2_Axtinib_150_19hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse2_Pos3_Axtinib_150_19hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse1_Axtinib_150_19hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse2_Axtinib_150_20hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse3_Axtinib_150_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos0_Axtinib_150_19hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos1_Axtinib_150_19hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos2_Axtinib_150_19hrs_Calcein_FM464';
+            '23-06-2016_Timelapse_3_Axitinib_36hr_Calcein_FM464'};
+
+bb94_order = {'29-06-2016_TimeLapse1_Pos0_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse1_Pos1_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse1_Pos2_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos0_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos1_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos2_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos0_BB94_23hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos2_BB94_23hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos3_BB94_23hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos0_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos1_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos2_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos3_BB94_21hrs_Calcein_FM464'};
+
+all_order = [dmso_order; axt_order; bb94_order];
+
+flist = dir('dataset_ecto_stats-*.mat');
+fnames = {flist.name}.';
+
+load(fnames{end});
+
+%% Sort and only keep matched tracks for now
+matchCells = arrayfun(@(x)(find(strcmpi(all_order,x.name))), ecto_size_stats, 'UniformOutput',false);
+matchTracks = arrayfun(@(x)(find(strcmpi(all_order,x.name))), ecto_track_stats, 'UniformOutput',false);
+
+bMatchedS = cellfun(@(x)(~isempty(x)), matchCells);
+bMatchedT = cellfun(@(x)(~isempty(x)), matchTracks);
+
+chk_size_stats = ecto_size_stats(bMatchedS);
+chk_track_stats = ecto_track_stats(bMatchedT);
+
+[~,invMatch] = sort(vertcat(matchCells{bMatchedS}));
+chk_size_stats = chk_size_stats(invMatch);
+
+[~,invMatch] = sort(vertcat(matchTracks{bMatchedT}));
+chk_track_stats = chk_track_stats(invMatch);
+
+%% Drop empty data
+bValidEctoS = arrayfun(@(x)(~isempty(x.ecto.vsizes)), chk_size_stats);
+bValidSkelS = arrayfun(@(x)(~isempty(x.skel.vsizes)), chk_size_stats);
+
+bValidEctoT = arrayfun(@(x)(~isempty(x.ecto.mean_sizes)), chk_track_stats);
+bValidSkelT = arrayfun(@(x)(~isempty(x.skel.mean_sizes)), chk_track_stats);
+
+bError = (bValidEctoS & ~bValidSkelS);
+err_names = arrayfun(@(x)(x.abrv), chk_size_stats(bError), 'UniformOutput',false);
+if ( any(bError) )
+    fprintf('WARNING: Invalid data detected: No skeletogenic segmentations: (%s)\n', strjoin(err_names,','));
+end
+
+chk_size_stats = chk_size_stats(bValidEctoS);
+
+bError = (bValidEctoT & ~bValidSkelT);
+err_names = arrayfun(@(x)(x.abrv), chk_size_stats(bError), 'UniformOutput',false);
+if ( any(bError) )
+    fprintf('WARNING: Invalid data detected: No skeletogenic segmentations: (%s)\n', strjoin(err_names,','));
+end
+
+chk_track_stats = chk_track_stats(bValidEctoT);
+
+%%
+exp_types = arrayfun(@(x)(regexpi(x.abrv,'(\w*?)_','tokens','once')), chk_size_stats, 'UniformOutput',false);
+exp_types = vertcat(exp_types{:});
+
+ecto_size_info = vertcat(chk_size_stats.ecto);
+skel_size_info = vertcat(chk_size_stats.skel);
+endo_size_info = vertcat(chk_size_stats.endo);
+
+exp_types_track = arrayfun(@(x)(regexpi(x.abrv,'(\w*?)_','tokens','once')), chk_track_stats, 'UniformOutput',false);
+exp_types_track = vertcat(exp_types_track{:});
+
+ecto_track_info = vertcat(chk_track_stats.ecto);
+skel_track_info = vertcat(chk_track_stats.skel);
+endo_track_info = vertcat(chk_track_stats.endo);
+
+%%
+outFile = 'ecto_size_stats_axtinib.csv';
+fid = fopen(outFile, 'wt');
+
+% heading = {'experiment','exp_short','type','vid',};
+heading = {'experiment','exp_short','type','region','vid','size'};
+
+hstr = strjoin(heading,',');
+fprintf(fid, '%s\n', hstr);
+
+for i=1:length(chk_size_stats)
+    exp_name = chk_size_stats(i).name;
+    exp_short = chk_size_stats(i).abrv;
+    
+    type_cell = regexpi(chk_size_stats(i).abrv,'(\w*?)_','tokens','once');
+    exp_type = type_cell{1};
+    
+    if ( strcmpi(exp_type,'BB94') )
+        continue;
+    end
+    
+    exp_type = replace(exp_type, 'DMSO','Control');
+    exp_type = replace(exp_type, 'Axtinib','VEGFR Inhibition');
+    exp_type = replace(exp_type, 'BB94','Metalloproteinase Inhibition');
+    
+    vid = 1;
+    for j=1:length(chk_size_stats(i).skel.vsizes)
+        fprintf(fid, '%s,%s,%s,%s,', exp_name, exp_short, exp_type, 'Mesoderm');
+        fprintf(fid, '%d,', vid);
+        
+        fprintf(fid, '%f', chk_size_stats(i).skel.vsizes(j));
+        
+        fprintf(fid, '\n');
+        
+        vid = vid + 1;
+    end
+    
+    for j=1:length(chk_size_stats(i).ecto.vsizes)
+        fprintf(fid, '%s,%s,%s,%s,', exp_name, exp_short, exp_type, 'Ectoderm');
+        fprintf(fid, '%d,', vid);
+        
+        fprintf(fid, '%f', chk_size_stats(i).ecto.vsizes(j));
+        
+        fprintf(fid, '\n');
+        
+        vid = vid + 1;
+    end
+end
+
+fclose(fid);
+
+%%
+outFile = 'ecto_track_stats_axtinib.csv';
+fid = fopen(outFile, 'wt');
+
+% heading = {'experiment','exp_short','type','vid',};
+heading = {'experiment','exp_short','type','region','tid',...
+           'avg_size','avg_speed','path_ratio',...
+           'win_path_ratio','dir_index','dir_avg_index',...
+           'msd_D','msd_R',...
+           'nimsd_D','nimsd_alpha','nimsd_R'};
+
+hstr = strjoin(heading,',');
+fprintf(fid, '%s\n', hstr);
+
+didx_idx = 9;
+
+for i=1:length(chk_track_stats)
+    exp_name = chk_track_stats(i).name;
+    exp_short = chk_track_stats(i).abrv;
+    
+    type_cell = regexpi(chk_track_stats(i).abrv,'(\w*?)_','tokens','once');
+    exp_type = type_cell{1};
+    
+    exp_type = replace(exp_type, 'DMSO','Control');
+    exp_type = replace(exp_type, 'Axtinib','VEGFR Inhibition');
+    exp_type = replace(exp_type, 'BB94','Metalloproteinase Inhibition');
+    
+    tid = 1;
+    for j=1:length(chk_track_stats(i).skel.mean_sizes)
+        fprintf(fid, '%s,%s,%s,%s,', exp_name, exp_short, exp_type, 'Mesoderm');
+        fprintf(fid, '%d,', tid);
+        
+        path_rat = chk_track_stats(i).skel.direct_len(j) / chk_track_stats(i).skel.path_len(j);
+        fprintf(fid, '%f,', chk_track_stats(i).skel.mean_sizes(j));
+        fprintf(fid, '%f,', chk_track_stats(i).skel.mean_inst_vel(j));
+        fprintf(fid, '%f,', path_rat);
+        
+        fprintf(fid, '%f,', chk_track_stats(i).skel.avg_wpath_ratio{j}(didx_idx));
+        fprintf(fid, '%f,', chk_track_stats(i).skel.avg_wdir_index{j}(didx_idx));
+        fprintf(fid, '%f,', chk_track_stats(i).skel.avg_wdir_mindex{j}(didx_idx));
+        
+        fprintf(fid, '%f,', chk_track_stats(i).skel.msd_D(j));
+        fprintf(fid, '%f,', chk_track_stats(i).skel.msd_R(j));
+        
+        fprintf(fid, '%f,', chk_track_stats(i).skel.nimsd_D(j));
+        fprintf(fid, '%f,', chk_track_stats(i).skel.nimsd_alpha(j));
+        fprintf(fid, '%f', chk_track_stats(i).skel.nimsd_R(j));
+        
+        fprintf(fid, '\n');
+        
+        tid = tid + 1;
+    end
+    
+    for j=1:length(chk_track_stats(i).ecto.mean_sizes)
+        fprintf(fid, '%s,%s,%s,%s,', exp_name, exp_short, exp_type, 'Ectoderm');
+        fprintf(fid, '%d,', tid);
+        
+        path_rat = chk_track_stats(i).ecto.direct_len(j) / chk_track_stats(i).ecto.path_len(j);
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.mean_sizes(j));
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.mean_inst_vel(j));
+        fprintf(fid, '%f,', path_rat);
+        
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.avg_wpath_ratio{j}(didx_idx));
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.avg_wdir_index{j}(didx_idx));
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.avg_wdir_mindex{j}(didx_idx));
+        
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.msd_D(j));
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.msd_R(j));
+        
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.nimsd_D(j));
+        fprintf(fid, '%f,', chk_track_stats(i).ecto.nimsd_alpha(j));
+        fprintf(fid, '%f', chk_track_stats(i).ecto.nimsd_R(j));
+        
+        fprintf(fid, '\n');
+        
+        tid = tid + 1;
+    end
+end
+
+fclose(fid);
diff --git a/+Stats/export_spicule_stats.m b/+Stats/export_spicule_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..bbfd6322068fe316e845d8a64ab0e94578052937
--- /dev/null
+++ b/+Stats/export_spicule_stats.m
@@ -0,0 +1,51 @@
+load spicule_stats.mat
+
+dists = [];
+abs_vel = [];
+
+abs_dist = [];
+inst_vel = [];
+for i=1:length(spicule_stats)
+    dists = [dists; spicule_stats(i).skel.sp_mean_dist];
+    abs_vel = [abs_vel; spicule_stats(i).skel.mean_inst_vel];
+    
+    abs_dist = [abs_dist; spicule_stats(i).skel.sp_absidelta];
+    inst_vel = [inst_vel; -spicule_stats(i).skel.sp_idelta];
+end
+
+ledges = 0:9;
+redges = 1:10;
+
+dist_grps = zeros(length(dists),1);
+for i=1:length(dists)
+    bIdx = dists(i) > ledges & dists(i) <= redges;
+    if ( ~any(bIdx) )
+        continue;
+    end
+    
+    dist_grps(i) = redges(bIdx);
+end
+
+bDropIdx = (dist_grps == 0);
+
+dist_grps = dist_grps(~bDropIdx);
+
+abs_vel_grp = abs_vel(~bDropIdx);
+abs_dist_grp = abs_dist(~bDropIdx);
+inst_vel_grp = inst_vel(~bDropIdx);
+
+%%
+outFile = 'spicule_stats.csv';
+fid = fopen(outFile, 'wt');
+
+heading = {'sp_dist','abs_vel','abs_dist','inst_vel'};
+
+hstr = strjoin(heading,',');
+fprintf(fid, '%s\n', hstr);
+
+for i=1:length(dist_grps)
+    fprintf(fid, '%f,%f,%f,%f\n', dist_grps(i), abs_vel_grp(i), abs_dist_grp(i), inst_vel_grp(i));
+end
+
+fclose(fid);
+
diff --git a/+Stats/load_all_ectostats.m b/+Stats/load_all_ectostats.m
new file mode 100644
index 0000000000000000000000000000000000000000..f8604d3c4b8d384f52e34439e387e38a5a36862f
--- /dev/null
+++ b/+Stats/load_all_ectostats.m
@@ -0,0 +1,39 @@
+ROOT = 'E:\Smadar\LeverJS';
+
+ecto_size_stats = [];
+ecto_track_stats = [];
+
+partial_prefix = 'dataset_ecto_p';
+
+flist = dir(fullfile(ROOT,'*.LEVER'));
+parfor i=1:length(flist)
+    [ecto_sizes, ecto_tracks] = Stats.load_ectostats(ROOT, flist(i).name);
+    if ( isempty(ecto_sizes) )
+        continue;
+    end
+    
+    Stats.parsave(partial_prefix, i, {'ecto_sizes','ecto_tracks'}, {ecto_sizes, ecto_tracks});
+end
+
+% flist = dir(fullfile(ROOT,'*.LEVER'));
+% for i=1:length(flist)
+%     [ecto_sizes, ecto_tracks] = Stats.load_ectostats(ROOT, flist(i).name);
+%     if ( isempty(ecto_sizes) )
+%         continue;
+%     end
+%     
+%     Stats.parsave('dataset_ecto_p', i, {'ecto_sizes','ecto_tracks'}, {ecto_sizes, ecto_tracks});
+% end
+
+date_suffix = ['-' datestr(now(),'yyyy-mm-dd')];
+
+matList = dir([partial_prefix '*.mat']);
+for i=1:length(matList)
+    s = load(matList(i).name);
+    
+    ecto_size_stats = [ecto_size_stats; s.ecto_sizes];
+    ecto_track_stats = [ecto_track_stats; s.ecto_tracks];
+    
+    outname = ['dataset_ecto_stats' date_suffix '.mat'];
+    save(outname, 'ecto_size_stats','ecto_track_stats');
+end
diff --git a/+Stats/load_all_stats.m b/+Stats/load_all_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..40cc6275ade423f2bab9305671adcb02362fb72e
--- /dev/null
+++ b/+Stats/load_all_stats.m
@@ -0,0 +1,22 @@
+ROOT = 'E:\Smadar\LeverJS';
+
+% if ( ~exist('dataset_stats.mat','file') )
+    size_stats = [];
+    track_stats = [];
+% else
+%     load dataset_stats.mat
+% end
+
+flist = dir(fullfile(ROOT,'*.LEVER'));
+for i=1:length(flist)
+%     if strncmpi(flist(i).name,'27',2)
+%         break;
+%     end
+
+    [ds_sizes,ds_tracks] = load_stats(ROOT, flist(i).name);
+    
+    size_stats = [size_stats; ds_sizes];
+    track_stats = [track_stats; ds_tracks];
+    
+    save('dataset_stats.mat', 'size_stats','track_stats');
+end
diff --git a/+Stats/load_ectostats.m b/+Stats/load_ectostats.m
new file mode 100644
index 0000000000000000000000000000000000000000..9b22f6d78181dd8e3a7fcc6864f8b2c6fc52a067
--- /dev/null
+++ b/+Stats/load_ectostats.m
@@ -0,0 +1,420 @@
+function [size_stats, track_stats] = load_ectostats(ROOT, filename)
+    leverFile = fullfile(ROOT,filename);
+    
+    size_stats = [];
+    track_stats = [];
+
+    %% Load constants
+    conn = database(leverFile, '','', 'org.sqlite.JDBC', 'jdbc:sqlite:');
+    CONSTANTS = Read.getConstants(conn);
+    
+    %% Setup time and pixel resolution info
+    cutoff = 60*15;
+    bKeep = CONSTANTS.imageData.TimeStampDelta(1,1,:) <= cutoff;
+    
+    pxSize = CONSTANTS.imageData.PixelPhysicalSize;
+    pxVol = prod(pxSize);
+    
+    tsmp = squeeze(CONSTANTS.imageData.TimeStampDelta(1,1,:));
+    
+    %% Set number of frames to consider
+    keeplen = nnz(bKeep);
+    
+    %% Create abbreviation for dataset
+    datasetName = CONSTANTS.imageData.DatasetName;
+    abrv = Stats.abbreviateName(datasetName);
+    
+    %% Estimate parallelogram mask for first-frame image
+    imagePath = CONSTANTS.imageData.imageDir;
+    im = MicroscopeData.Reader(imagePath, 'timeRange',[1,1]);
+    
+    parallelMask = Segment.llsm_mask(im);
+    % HACK: Drop messy top/bottom slices in mask
+    parallelMask(:,:,1) = false;
+    parallelMask(:,:,end) = false;
+    
+    %% Get ecto/endo regions
+    dataPath = 'Data';
+    maskPath = fullfile(dataPath, 'ectoMasks');
+    
+    ectoPath = fullfile(maskPath, [datasetName '_ectoregion.mat']);
+    endoPath = fullfile(maskPath, [datasetName '_endoregion.mat']);
+    
+    bHasEcto = exist(ectoPath, 'file');
+    bHasEndo = exist(endoPath, 'file');
+    
+    endoInfo = [];
+    bwEndo = false(size(parallelMask));
+    
+    size_stats = [];
+    if ( ~bHasEcto )
+        return;
+    end
+    
+    %% Set frame cutoff should be no more than 5 minutes
+    cutoff = 60 * 5;
+    bKeep = CONSTANTS.imageData.TimeStampDelta(1,1,:) <= cutoff;
+    keeplen = nnz(bKeep);
+    
+    %% Load edge/track info
+    trackInfoPath = fullfile(dataPath, [datasetName '_trackinfo.mat']);
+    if ( ~exist(trackInfoPath, 'file') )
+        return;
+    end
+    
+    load(fullfile(dataPath, [datasetName '_edgeinfo.mat']));
+    load(trackInfoPath);
+    
+    %% Load spicule if it exists
+    sp_struct = [];
+    if ( exist(fullfile(dataPath, [datasetName '_spicule.json']), 'file') )
+        sp_json = fileread(fullfile(dataPath, [datasetName '_spicule.json']));
+        sp_struct = jsondecode(sp_json);
+        [cs,rs,als,Rs] = calc_spicule_coords(sp_struct, pxSize);
+    end
+    
+    %% Load ectoderm and endoderm region info
+    ectoInfo = load(ectoPath);
+    bwEcto = (parallelMask & ectoInfo.regionInfo.bwMask(:,:,:,1));
+    
+    if ( bHasEndo )
+        endoInfo = load(endoPath);
+        bwEndo = (parallelMask & endoInfo.regionInfo.bwMask(:,:,:,1));
+    end
+    
+    bwSkel = (parallelMask & ~bwEcto & ~bwEndo);
+    
+    %% Initialize size stats structures
+    size_stats = struct('name',{datasetName}, 'abrv',{abrv}, 'tsmp',{tsmp}, 'ecto',{[]}, 'skel',{[]}, 'endo',{[]});
+    size_stats.ecto = struct('rsize',{[]}, 'vsizes',{[]}, 'count',{[]}, 'com',{[]});
+    size_stats.skel = struct('rsize',{[]}, 'vsizes',{[]}, 'count',{[]}, 'com',{[]});
+    size_stats.endo = struct('rsize',{[]}, 'vsizes',{[]}, 'count',{[]}, 'com',{[]});
+    
+    size_stats.ecto.rsize = nnz(bwEcto) * pxVol;
+    size_stats.skel.rsize = nnz(bwSkel) * pxVol;
+    size_stats.endo.rsize = nnz(bwEndo) * pxVol;
+    
+    %% Initialize track stat structures
+    track_stats = struct('name',{datasetName}, 'abrv',{abrv}, 'tsmp',{tsmp}, 'ecto',{[]}, 'skel',{[]}, 'endo',{[]});
+    track_stats.ecto = struct('mean_sizes',{[]}, 'track_lens',{[]}, 'mean_inst_vel',{[]}, 'path_len',{[]}, ...
+                              'brownian_var',{[]}, 'brownian_cov',{[]}, 'direct_len',{[]}, 'start_pos',{[]},...
+                              'ivel_sm',{[]}, 'isize_sm',{[]},...
+                              'msd_D',{[]}, 'msd_sig',{[]}, 'msd_R',{[]},...
+                              'nimsd_D',{[]}, 'nimsd_alpha',{[]}, 'nimsd_R',{[]},...
+                              'wlen',{[]}, 'avg_path_wlen',{[]}, 'avg_max_wdist',{[]}, 'avg_wdir_index',{[]},...
+                              'avg_wdir_mindex',{[]}, 'avg_wpath_ratio',{[]},...
+                              'sp_mean_dist',{[]}, 'sp_idelta',{[]}, 'sp_tdelta',{[]}, 'sp_dstd',{[]}, 'sp_absidelta',{[]});
+    track_stats.skel = struct('mean_sizes',{[]}, 'track_lens',{[]}, 'mean_inst_vel',{[]}, 'path_len',{[]}, ...
+                              'brownian_var',{[]}, 'brownian_cov',{[]}, 'direct_len',{[]}, 'start_pos',{[]},...
+                              'ivel_sm',{[]}, 'isize_sm',{[]},...
+                              'msd_D',{[]}, 'msd_sig',{[]}, 'msd_R',{[]},...
+                              'nimsd_D',{[]}, 'nimsd_alpha',{[]}, 'nimsd_R',{[]},...
+                              'wlen',{[]}, 'avg_path_wlen',{[]}, 'avg_max_wdist',{[]}, 'avg_wdir_index',{[]},...
+                              'avg_wdir_mindex',{[]}, 'avg_wpath_ratio',{[]},...
+                              'sp_mean_dist',{[]}, 'sp_idelta',{[]}, 'sp_tdelta',{[]}, 'sp_dstd',{[]}, 'sp_absidelta',{[]});
+    track_stats.endo = struct('mean_sizes',{[]}, 'track_lens',{[]}, 'mean_inst_vel',{[]}, 'path_len',{[]}, ...
+                              'brownian_var',{[]}, 'brownian_cov',{[]}, 'direct_len',{[]}, 'start_pos',{[]},...
+                              'ivel_sm',{[]}, 'isize_sm',{[]},...
+                              'msd_D',{[]}, 'msd_sig',{[]}, 'msd_R',{[]},...
+                              'nimsd_D',{[]}, 'nimsd_alpha',{[]}, 'nimsd_R',{[]},...
+                              'wlen',{[]}, 'avg_path_wlen',{[]}, 'avg_max_wdist',{[]}, 'avg_wdir_index',{[]},...
+                              'avg_wdir_mindex',{[]}, 'avg_wpath_ratio',{[]},...
+                              'sp_mean_dist',{[]}, 'sp_idelta',{[]}, 'sp_tdelta',{[]}, 'sp_dstd',{[]}, 'sp_absidelta',{[]});
+
+
+    %% Read per-frame vesicle info
+    bEcto = false(0,1);
+    bSkel = false(0,1);
+    bEndo = false(0,1);
+    
+    coms = zeros(0,3);
+    ctimes = zeros(0,1);
+    csizes = zeros(0,1);
+    cframes = zeros(0,1);
+    
+    imSize = [size(im,1), size(im,2), size(im,3)];
+    for t=1:keeplen
+        Cells = Read.getCells3DTime(conn,t);
+        if ( isempty(Cells) )
+            return;
+        end
+    
+        cellsIdx = cellfun(@(x)(Utils.CoordToInd(imSize, double(Utils.SwapXY_RC(x)))), {Cells.pts}.', 'UniformOutput',false);
+        cids = vertcat(Cells.cellID);
+    
+        %% Assign vesicles to regions by max overlap
+        nEcto = cellfun(@(x)(nnz(bwEcto(x))), cellsIdx);
+        nSkel = cellfun(@(x)(nnz(bwSkel(x))), cellsIdx);
+        nEndo = cellfun(@(x)(nnz(bwEndo(x))), cellsIdx);
+
+        bEcto(cids) = (nEcto >= nSkel & nEcto >= nEndo);
+        bSkel(cids) = (nSkel >= nEndo & ~bEcto(cids));
+        bEndo(cids) = (~bEcto(cids) & ~bSkel(cids));
+        
+        coms(cids,:) = horzcat(Cells.centroid).' .* pxSize;
+        csizes(cids) = vertcat(Cells.area) .* pxVol;
+        ctimes(cids) = tsmp(vertcat(Cells.time));
+        cframes(cids) = vertcat(Cells.time);
+    end
+    
+    bFrame1 = (cframes == 1);
+    
+    %% Ecto stats
+    size_stats.ecto.vsizes = csizes(bEcto & bFrame1);
+    size_stats.ecto.count = nnz(bEcto & bFrame1);
+    size_stats.ecto.com = coms(bEcto,:);
+    
+    %% Skel stats
+    size_stats.skel.vsizes = csizes(bSkel & bFrame1);
+    size_stats.skel.count = nnz(bSkel & bFrame1);
+    size_stats.skel.com = coms(bSkel,:);
+    
+    %% Endo stats
+    size_stats.endo.vsizes = csizes(bEndo & bFrame1);
+    size_stats.endo.count = nnz(bEndo & bFrame1);
+    size_stats.endo.com = coms(bEndo,:);
+    
+    %% Tracking stats
+    %% Skip if movie is >= 7fps
+    medTsmp = median(diff(tsmp));
+    if ( medTsmp >= 7.0 )
+        return;
+    end
+    
+    %% Get tracks to keep
+    minTrackLen = 7;
+    
+    tlens = cellfun(@(x)(length(x)), trackInfo);
+    bKeep = (tlens >= minTrackLen);
+    
+    lastId = length(csizes);
+    trackInfo = trackInfo(bKeep);
+    for i=1:length(trackInfo)
+        bValid = (trackInfo{i} <= lastId);
+        trackInfo{i} = trackInfo{i}(bValid);
+    end
+    
+    bValidInfo = cellfun(@(x)(length(x) >= 5), trackInfo);
+    trackInfo = trackInfo(bValidInfo);
+    
+    %% Assign tracks to region by majority vote
+    tnEcto = cellfun(@(x)(sum(bEcto(x))), trackInfo);
+    tnSkel = cellfun(@(x)(sum(bSkel(x))), trackInfo);
+    tnEndo = cellfun(@(x)(sum(bEndo(x))), trackInfo);
+    
+    btEcto = (tnEcto >= tnSkel & tnEcto >= tnEndo);
+    btSkel = (tnSkel >= tnEndo & ~btEcto);
+    btEndo = (~btEcto & ~btSkel);
+    
+    % Setup track info to put into groups
+    track_lens = zeros(length(trackInfo),1);
+    track_inst_vel = zeros(length(trackInfo),1);
+    track_mean_size = zeros(length(trackInfo),1);
+    track_path_len = zeros(length(trackInfo),1);
+    track_brn_var = zeros(length(trackInfo),1);
+    track_brn_cov = zeros(3,3,length(trackInfo));
+    track_direct_len = zeros(length(trackInfo),1);
+    track_start_pos = zeros(length(trackInfo),3);
+    
+    track_sp_dists = zeros(length(trackInfo),1);
+    track_sp_idelta = zeros(length(trackInfo),1);
+    track_sp_tdelta = zeros(length(trackInfo),1);
+    track_sp_dstd = zeros(length(trackInfo),1);
+    track_sp_iabsdelta = zeros(length(trackInfo),1);
+    track_sp_ivel = zeros(length(trackInfo),1);
+    
+    track_wsec = (15:9:105);
+    track_wlen = round(track_wsec ./ medTsmp);
+    track_avg_path_wlen = cell(length(trackInfo),1);
+    track_avg_max_wdisp = cell(length(trackInfo),1);
+    track_avg_wdir_index = cell(length(trackInfo),1);
+    track_avg_wdir_mindex = cell(length(trackInfo),1);
+    track_avg_wpath_rat = cell(length(trackInfo),1);
+    
+    %% TODO: Setup and compute MSD fit for each track
+    msd_gamma = 3;
+    track_msd_D = zeros(length(trackInfo),1);
+    track_msd_sig = zeros(length(trackInfo),1);
+    track_msd_R = zeros(length(trackInfo),1);
+    
+    track_nimsd_D = zeros(length(trackInfo),1);
+    track_nimsd_alpha = zeros(length(trackInfo),1);
+    track_nimsd_R = zeros(length(trackInfo),1);
+    
+    for i=1:length(trackInfo)
+        %% Compute per-track info
+        cids = trackInfo{i};
+        
+        ttime = ctimes(cids);
+        tsize = csizes(cids);
+        tpos = coms(cids,:);
+        
+        tdt = diff(ttime);
+        
+        if ( ~isempty(sp_struct) )
+            T = sp_struct.root .* pxSize;
+            tds = spicule_distances(cs,Rs,rs,als, T,tpos);
+            tds_delta = diff(tds,1);
+            
+            track_sp_dists(i) = mean(tds);
+            track_sp_idelta(i) = mean(tds_delta);
+            track_sp_tdelta(i) = tds(end)-tds(1);
+            track_sp_dstd(i) = std(tds);
+            track_sp_iabsdelta(i) = mean(abs(tds_delta));
+            track_sp_ivel(i) = mean(tds_delta ./ tdt);
+        end
+        
+        tdx = diff(tpos,1,1);
+        tmsd = cumsum(sum(tdx.^2, 2));
+        
+        tmot = sqrt(sum(tdx.^2, 2));
+        tvel = tmot ./ tdt;
+        
+        nrmtdx = tdx ./ sqrt(tdt);
+        brntvel = sqrt(sum(nrmtdx.^2, 2));
+%         brntvel = tmot ./ sqrt(dt);
+
+        % Compute Diffusion coefficient (with bias)
+        x1 = cumsum(tdt);
+        x2 = ones(size(tdt,1),1);
+        [b,~,~,~,rstat] = regress(tmsd, [x1 x2]);
+        
+        track_msd_D(i) = b(1)/(2*msd_gamma);
+        track_msd_sig(i) = b(2)/(2*msd_gamma);
+        track_msd_R(i) = sqrt(rstat(1));
+        
+        % Compute non-ideal diffusion coeffient
+        x1 = log(cumsum(tdt));
+        x2 = ones(size(tdt,1),1);
+        [b,~,~,~,rstat] = regress(log(tmsd), [x1 x2]);
+        
+        track_nimsd_alpha(i) = b(1);
+        track_nimsd_D(i) = exp(b(2))/(2*msd_gamma);
+        track_nimsd_R(i) = sqrt(rstat(1));
+
+        track_start_pos(i,:) = tpos(1,:);
+        track_lens(i) = length(trackInfo);
+        
+        track_inst_vel(i) = mean(tvel);
+        track_mean_size(i) = mean(tsize);
+        
+        track_path_len(i) = sum(tmot, 1);
+        track_direct_len(i) = sqrt(sum((tpos(end,:)-tpos(1,:)).^2, 2));
+        
+        track_brn_var(i) = var(brntvel);
+        track_brn_cov(:,:,i) = cov(nrmtdx);
+        
+        for j=1:length(track_wlen)
+            wlen = track_wlen(j);
+%             if ( wlen > length(ttime) )
+%                 break;
+%             end
+            
+            nwin = max(length(ttime) - wlen + 1, 1);
+            max_wdisp = zeros(nwin,1);
+            mean_wdisp = zeros(nwin,1);
+            path_wlen = zeros(nwin,1);
+            se_wdisp = zeros(nwin,1);
+            for k=1:nwin
+                idxs = k:min(k+wlen-1, length(ttime));
+                dsps = pdist(tpos(idxs,:));
+                
+                path_wlen(k) = sum(tmot(idxs(1:end-1)));
+                max_wdisp(k) = max(dsps);
+                se_wdisp(k) = sqrt(sum((tpos(idxs(end))-tpos(idxs(1))).^2, 2));
+            end
+            
+            wdir_index = max_wdisp ./ path_wlen;
+            wdir_mindex = mean_wdisp ./ path_wlen;
+            wpath_rat = se_wdisp ./ path_wlen;
+            
+            track_avg_max_wdisp{i} = [track_avg_max_wdisp{i} mean(max_wdisp)];
+            track_avg_path_wlen{i} = [track_avg_path_wlen{i} mean(path_wlen)];
+            track_avg_wdir_index{i} = [track_avg_wdir_index{i} mean(wdir_index)];
+            track_avg_wdir_mindex{i} = [track_avg_wdir_mindex{i} mean(wdir_mindex)];
+            track_avg_wpath_rat{i} = [track_avg_wpath_rat{i} mean(wpath_rat)];
+        end
+    end
+    
+    %% Ecto stats
+    track_stats.ecto.mean_sizes = track_mean_size(btEcto);
+    track_stats.ecto.track_lens = track_lens(btEcto);
+    track_stats.ecto.mean_inst_vel = track_inst_vel(btEcto);
+    track_stats.ecto.path_len = track_path_len(btEcto);
+    track_stats.ecto.direct_len = track_direct_len(btEcto);
+    track_stats.ecto.brownian_var = track_brn_var(btEcto);
+    track_stats.ecto.brownian_cov = track_brn_cov(:,:,btEcto);
+    track_stats.ecto.start_pos = track_start_pos(btEcto,:);
+    track_stats.ecto.sp_mean_dist = track_sp_dists(btEcto);
+    track_stats.ecto.sp_idelta = track_sp_ivel(btEcto);
+    track_stats.ecto.sp_tdelta = track_sp_tdelta(btEcto);
+    track_stats.ecto.sp_dstd = track_sp_dstd(btEcto);
+    track_stats.ecto.sp_absidelta = track_sp_iabsdelta(btEcto);
+    
+    track_stats.ecto.msd_D = track_msd_D(btEcto);
+    track_stats.ecto.msd_sig = track_msd_sig(btEcto);
+    track_stats.ecto.msd_R = track_msd_R(btEcto);
+    track_stats.ecto.nimsd_D = track_nimsd_D(btEcto);
+    track_stats.ecto.nimsd_alpha = track_nimsd_alpha(btEcto);
+    track_stats.ecto.nimsd_R = track_nimsd_R(btEcto);
+    
+%     track_stats.ecto.wlen = track_wlen;
+    track_stats.ecto.avg_path_wlen = track_avg_path_wlen(btEcto);
+    track_stats.ecto.avg_max_wdist = track_avg_max_wdisp(btEcto);
+    track_stats.ecto.avg_wdir_index = track_avg_wdir_index(btEcto);
+    track_stats.ecto.avg_wdir_mindex = track_avg_wdir_mindex(btEcto);
+    track_stats.ecto.avg_wpath_ratio = track_avg_wpath_rat(btEcto);
+    
+    
+    %% Skel stats
+    track_stats.skel.mean_sizes = track_mean_size(btSkel);
+    track_stats.skel.track_lens = track_lens(btSkel);
+    track_stats.skel.mean_inst_vel = track_inst_vel(btSkel);
+    track_stats.skel.path_len = track_path_len(btSkel);
+    track_stats.skel.direct_len = track_direct_len(btSkel);
+    track_stats.skel.brownian_var = track_brn_var(btSkel);
+    track_stats.skel.brownian_cov = track_brn_cov(:,:,btSkel);
+    track_stats.skel.start_pos = track_start_pos(btSkel,:);
+    track_stats.skel.sp_mean_dist = track_sp_dists(btSkel);
+    track_stats.skel.sp_idelta = track_sp_ivel(btSkel);
+    track_stats.skel.sp_tdelta = track_sp_tdelta(btSkel);
+    track_stats.skel.sp_dstd = track_sp_dstd(btSkel);
+    track_stats.skel.sp_absidelta = track_sp_iabsdelta(btSkel);
+    
+    track_stats.skel.msd_D = track_msd_D(btSkel);
+    track_stats.skel.msd_sig = track_msd_sig(btSkel);
+    track_stats.skel.msd_R = track_msd_R(btSkel);
+    track_stats.skel.nimsd_D = track_nimsd_D(btSkel);
+    track_stats.skel.nimsd_alpha = track_nimsd_alpha(btSkel);
+    track_stats.skel.nimsd_R = track_nimsd_R(btSkel);
+    
+    %     track_stats.ecto.wlen = track_wlen;
+    track_stats.skel.avg_path_wlen = track_avg_path_wlen(btSkel);
+    track_stats.skel.avg_max_wdist = track_avg_max_wdisp(btSkel);
+    track_stats.skel.avg_wdir_index = track_avg_wdir_index(btSkel);
+    track_stats.skel.avg_wdir_mindex = track_avg_wdir_mindex(btSkel);
+    track_stats.skel.avg_wpath_ratio = track_avg_wpath_rat(btSkel);
+    
+    %% Endo stats
+    track_stats.endo.mean_sizes = track_mean_size(btEndo);
+    track_stats.endo.track_lens = track_lens(btEndo);
+    track_stats.endo.mean_inst_vel = track_inst_vel(btEndo);
+    track_stats.endo.path_len = track_path_len(btEndo);
+    track_stats.endo.direct_len = track_direct_len(btEndo);
+    track_stats.endo.brownian_var = track_brn_var(btEndo);
+    track_stats.endo.brownian_cov = track_brn_cov(:,:,btEndo);
+    track_stats.endo.start_pos = track_start_pos(btEndo,:);
+    track_stats.endo.sp_mean_dist = track_sp_dists(btEndo);
+    track_stats.endo.sp_idelta = track_sp_ivel(btEndo);
+    track_stats.endo.sp_tdelta = track_sp_tdelta(btEndo);
+    track_stats.endo.sp_dstd = track_sp_dstd(btEndo);
+    track_stats.endo.sp_absidelta = track_sp_iabsdelta(btEndo);
+    
+    track_stats.endo.msd_D = track_msd_D(btEndo);
+    track_stats.endo.msd_sig = track_msd_sig(btEndo);
+    track_stats.endo.msd_R = track_msd_R(btEndo);
+    track_stats.endo.nimsd_D = track_nimsd_D(btEndo);
+    track_stats.endo.nimsd_alpha = track_nimsd_alpha(btEndo);
+    track_stats.endo.nimsd_R = track_nimsd_R(btEndo);
+    
+end
diff --git a/+Stats/load_stats.m b/+Stats/load_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..0ae244124ed9c4686ea06ede814b2f8c1e9dfc1a
--- /dev/null
+++ b/+Stats/load_stats.m
@@ -0,0 +1,135 @@
+function [size_stats,track_stats] = load_stats(ROOT,filename)
+    leverFile = fullfile(ROOT,filename);
+
+    conn = database(leverFile, '','', 'org.sqlite.JDBC', 'jdbc:sqlite:');
+    CONSTANTS = Read.getConstants(conn);
+    
+    cutoff = 60*15;
+    bKeep = CONSTANTS.imageData.TimeStampDelta(1,1,:) <= cutoff;
+    
+    pixSize = CONSTANTS.imageData.PixelPhysicalSize;
+    tsmp = squeeze(CONSTANTS.imageData.TimeStampDelta(1,1,:));
+    
+    keeplen = nnz(bKeep);
+    
+    datasetName = CONSTANTS.imageData.DatasetName;
+    abrv = Stats.abbreviateName(datasetName);
+    size_stats = struct('name',{datasetName}, 'abrv',{abrv}, 'sizes',{[]}, 'cpf',{[]}, 'vpf',{[]}, 'mvpf',{[]});
+    track_stats = struct('name',{datasetName}, 'abrv',{abrv}, 'sizes',{[]}, 'tlen',{[]}, 'ivel',{[]}, ...
+                        'tvel',{[]}, 'slvec',{[]}, 'slvel',{[]}, 'slmot',{[]}, 'slpos',{[]},...
+                        'brnVar',{[]}, 'brnCov',{[]});
+
+    %% Load edge/track info
+    if ( ~exist([datasetName '_trackinfo.mat'], 'file') )
+        return;
+    end
+    
+    load([datasetName '_edgeinfo.mat']);
+    load([datasetName '_trackinfo.mat']);
+    
+    %% TODO: What is an appropriate minimum track length
+    minTrackLen = 7;
+    
+    tlens = cellfun(@(x)(length(x)), trackInfo);
+    bKeep = (tlens >= minTrackLen);
+    
+    trackInfo = trackInfo(bKeep);
+    
+    numCells = max(cellfun(@(x)(max(x)), trackInfo));
+    coms = zeros(numCells,3);
+    vols = zeros(numCells,1);
+    times = zeros(numCells,1);
+    
+    %% Get cell info and size stats
+    for t=1:keeplen
+        Cells = Read.getCells3DTime(conn,t);
+        
+        size_stats.cpf = [size_stats.cpf; 0];
+        size_stats.vpf = [size_stats.vpf; 0];
+        size_stats.mvpf = [size_stats.mvpf; 0];
+        
+        if ( isempty(Cells) )
+            continue;
+        end
+        
+        ids = vertcat(Cells.cellID);
+        cellSizes = vertcat(Cells.area) .* prod(pixSize);
+        
+        totalSize = sum(cellSizes);
+        meanSize = mean(cellSizes);
+        countVes = length(cellSizes);
+        
+        size_stats.cpf(end) = countVes;
+        size_stats.vpf(end) = totalSize;
+        size_stats.mvpf(end) = meanSize;
+        size_stats.sizes = [size_stats.sizes; cellSizes];
+        
+        coms(ids,:) = horzcat(Cells.centroid).' .* pixSize;
+        vols(ids,:) = cellSizes;
+        times(ids,:) = t;
+    end
+    
+    %% Track length
+    track_stats.tlen = zeros(length(trackInfo),1);
+    for i=1:length(trackInfo)
+        track_stats.tlen(i) = length(trackInfo{i});
+    end
+    
+    %% Average vesicle sizes
+    track_stats.sizes = zeros(length(trackInfo),1);
+    for i=1:length(trackInfo)
+        hids = trackInfo{i};
+        track_stats.sizes(i) = mean(vols(hids));
+    end
+
+    %% Vesicle average instantaneous velocity
+    track_stats.ivel = zeros(length(trackInfo),1);
+    track_stats.tvel = zeros(length(trackInfo),1);
+    for i=1:length(trackInfo)
+        hids = trackInfo{i};
+        
+        dx = diff(coms(hids,:), 1);
+        dt = diff(tsmp(times(hids)), 1);
+        
+        vel = sqrt(sum(dx.^2, 2)) ./ dt;
+        
+        track_stats.ivel(i) = mean(vel);
+        track_stats.tvel(i) = sum(vel);
+    end
+    
+    %% Wiener proces variance (\sigma^2)
+    track_stats.brnVar = zeros(length(trackInfo),1);
+    track_stats.brnCov = zeros(3,3, length(trackInfo));
+    for i=1:length(trackInfo)
+        hids = trackInfo{i};
+        
+        dx = diff(coms(hids,:), 1);
+        dt = diff(tsmp(times(hids)), 1);
+        
+        vel = sum(dx.^2, 2) ./ sqrt(dt);
+        nrmdx = dx ./ repmat(sqrt(dt), 1,3);
+        
+        track_stats.brnVar(i) = var(vel);
+        track_stats.brnCov(:,:,i) = cov(nrmdx);
+    end
+
+    %% Vesicle total motion
+    track_stats.slvec = zeros(length(trackInfo),3);
+    track_stats.slvel = zeros(length(trackInfo),1);
+    track_stats.slpos = zeros(length(trackInfo),3);
+    track_stats.slmot = zeros(length(trackInfo),1);
+    for i=1:length(trackInfo)
+        hids = trackInfo{i};
+        
+        dvec = (coms(hids(end),:) - coms(hids(1),:));
+        
+        dx = sqrt(sum(dvec.^2, 2));
+        dt = (tsmp(times(hids(end))) - tsmp(times(hids(1))));
+
+        track_stats.slmot(i) = dx;
+        track_stats.slvel(i) = dx/dt;
+
+        track_stats.slvec(i,:) = dvec;
+        track_stats.slpos(i,:) = coms(hids(1),:);
+    end
+end
diff --git a/+Stats/make_all_trackinfo.m b/+Stats/make_all_trackinfo.m
new file mode 100644
index 0000000000000000000000000000000000000000..1fd73ad792dc68e1b7e57f025a4ef4def99d9ce4
--- /dev/null
+++ b/+Stats/make_all_trackinfo.m
@@ -0,0 +1,6 @@
+ROOT = 'E:\Smadar\LeverJS';
+
+flist = dir(fullfile(ROOT,'*.LEVER'));
+for i=1:length(flist)
+    Stats.make_trackinfo(ROOT,flist(i).name);
+end
diff --git a/+Stats/make_movie_table.m b/+Stats/make_movie_table.m
new file mode 100644
index 0000000000000000000000000000000000000000..2d8d2934956ed56d235e70eeddba06b3c4a7f8de
--- /dev/null
+++ b/+Stats/make_movie_table.m
@@ -0,0 +1,47 @@
+flist = dir('dataset_ecto_stats-*.mat');
+fnames = {flist.name}.';
+
+load(fnames{end});
+
+exp_types = arrayfun(@(x)(regexpi(x.abrv,'(\w*?)_','tokens','once')), ecto_size_stats, 'UniformOutput',false);
+exp_types = vertcat(exp_types{:});
+
+tsmp = arrayfun(@(x)(median(diff(x.tsmp))), ecto_size_stats);
+
+bExcludeMotion = (tsmp >= 7.0);
+exclMotSrt = repmat({'A'}, length(bExcludeMotion),1);
+exclMotSrt(bExcludeMotion) = {'B'};
+
+inclMotLbl = repmat({'Y'}, length(bExcludeMotion),1);
+inclMotLbl(bExcludeMotion) = {'N'};
+
+chkrows = arrayfun(@(x,y)([x,y]), exp_types, exclMotSrt, 'UniformOutput',false);
+chkrows = vertcat(chkrows{:});
+
+[~,srtidx] = sortrows(chkrows);
+
+fid = fopen('sup_table_info.csv','wt');
+fprintf(fid, 'Dataset,Treatment,dt (sec),First Frame Vesicles (Mesoderm),Motion Statistics,First Frame Vesicles (Ectoderm),Total Vesicles (Mesoderm),Total Vesicles (Ectoderm)\n');
+for i=1:length(srtidx)
+    idx = srtidx(i);
+    
+    datasetName = ecto_size_stats(idx).name;
+    
+    ectoVesicleCount = ecto_size_stats(idx).ecto.count;
+    skelVesicleCount = ecto_size_stats(idx).skel.count;
+    
+    if ( ~bExcludeMotion(idx) )
+        ectoTotalCount = length(ecto_track_stats(idx).ecto.mean_sizes);
+        skelTotalCount = length(ecto_track_stats(idx).skel.mean_sizes);
+
+        fprintf(fid, '%s,%s,%.2f,%s,%d,%d,%d,%d\n', datasetName, exp_types{idx}, tsmp(idx), inclMotLbl{idx},...
+                                                    skelVesicleCount, ectoVesicleCount, skelTotalCount, ectoTotalCount);
+    else
+        fprintf(fid, '%s,%s,%.2f,%s,%d,%d\n', datasetName, exp_types{idx}, tsmp(idx), inclMotLbl{idx}, skelVesicleCount, ectoVesicleCount);
+    end
+end
+
+fclose(fid);
+
+% 
+% 
\ No newline at end of file
diff --git a/+Stats/make_trackinfo.m b/+Stats/make_trackinfo.m
new file mode 100644
index 0000000000000000000000000000000000000000..474dd3c07be2e352f483ecf1fe57d191c83cc53c
--- /dev/null
+++ b/+Stats/make_trackinfo.m
@@ -0,0 +1,92 @@
+function make_trackinfo(ROOT,filename)
+    leverFile = fullfile(ROOT,filename);
+
+    conn = database(leverFile, '','', 'org.sqlite.JDBC', 'jdbc:sqlite:');
+    CONSTANTS = Read.getConstants(conn);
+    
+    cutoff = 60*15;
+    bKeep = CONSTANTS.imageData.TimeStampDelta(1,1,:) <= cutoff;
+    
+    keeplen = nnz(bKeep);
+    datasetName = CONSTANTS.imageData.DatasetName;
+    
+    %% Load track information from cells db table
+    fprintf('Loading tracking info... ');
+    cellTrackinfo = getCellTracks(conn);
+    if ( isempty(cellTrackinfo) )
+        return;
+    end
+    
+    szCells = max(cellTrackinfo.cellID);
+    fprintf('done\n');
+    
+    %% Load edge information from db
+    fprintf('Loading edge info... ');
+    edgeInfo = getEdgeInfo(conn, szCells);
+    save([datasetName '_edgeinfo.mat'], 'edgeInfo');
+    fprintf('done\n');
+    
+    %% Track lengths
+    trackLengths = zeros(1,max([cellTrackinfo.tid]));
+    for i=1:length(cellTrackinfo.tid)
+        trackLengths(cellTrackinfo.tid(i)) = trackLengths(cellTrackinfo.tid(i))+1;
+    end
+
+    validTracks = find(trackLengths > 5);
+    trackMap = containers.Map(validTracks,1:length(validTracks));
+
+    prg = Utils.CmdlnProgress(length(cellTrackinfo.tid));
+    trackInfo = cell(1,length(validTracks));
+    trackHash = cell(1,keeplen);
+    for i=1:length(cellTrackinfo.tid)
+        if ( cellTrackinfo.time(i) > keeplen )
+            prg.PrintProgress(i);
+            continue;
+        end
+        
+        if ( ~isKey(trackMap,cellTrackinfo.tid(i)) )
+            prg.PrintProgress(i);
+            continue;
+        end
+        
+        idx = trackMap(cellTrackinfo.tid(i));
+        
+        trackInfo{idx} = [trackInfo{idx} cellTrackinfo.cellID(i)];
+        trackHash{cellTrackinfo.time(i)} = [trackHash{cellTrackinfo.time(i)}; idx];
+        
+        prg.PrintProgress(i);
+    end
+    
+    prg.ClearProgress(true);
+    save([datasetName '_trackinfo.mat'], 'trackInfo', 'trackHash', 'validTracks', 'trackMap');
+end
+
+function trackInfo = getCellTracks(conn)
+    trackInfo = [];
+
+    cmd=['SELECT cellID,time,trackID FROM tblCells ORDER BY time,cellID'];
+    Q=ljsFetch(conn,cmd);
+    
+    if ( isempty(Q) )
+        return;
+    end
+    
+    trackInfo.cellID = vertcat(Q{:,1});
+    trackInfo.time = vertcat(Q{:,2});
+    trackInfo.tid = vertcat(Q{:,3});
+end
+
+function edgeInfo = getEdgeInfo(conn, szCells)
+    edgeInfo = [];
+
+    cmd=['SELECT cellID_src,cellID_dst,cost FROM tblCosts ORDER BY cellID_src'];
+    Q=ljsFetch(conn,cmd);
+    
+    edgeInfo.costs = sparse([Q{:,1}],[Q{:,2}],[Q{:,3}], szCells,szCells);
+    
+    
+    cmd=['SELECT cellID_src,cellID_dst,cost FROM tblDistCC ORDER BY cellID_src'];
+    Q=ljsFetch(conn,cmd);
+    edgeInfo.dists = sparse([Q{:,1}],[Q{:,2}],[Q{:,3}], szCells,szCells);
+end
+
diff --git a/+Stats/parsave.m b/+Stats/parsave.m
new file mode 100644
index 0000000000000000000000000000000000000000..e8e2de7265d6bf9c8b8052e3b1f41d06eb7bddbd
--- /dev/null
+++ b/+Stats/parsave.m
@@ -0,0 +1,9 @@
+function parsave(prefix, didx, names, vals)
+    sv_struct = [];
+    
+    for i=1:length(names)
+        sv_struct.(names{i}) = vals{i};
+    end
+    
+    save([prefix num2str(didx,'%02d') '.mat'], '-struct','sv_struct');
+end
diff --git a/+Stats/plot_directional_stats.m b/+Stats/plot_directional_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..47014013c2f7b18be765909fdee68242bd04c7cd
--- /dev/null
+++ b/+Stats/plot_directional_stats.m
@@ -0,0 +1,43 @@
+flist = dir('dataset_collate_tracks-*.mat');
+fnames = {flist.name}.';
+
+s = load(fnames{end});
+
+da = cell(length(s.track_data),1);
+for i=1:length(s.track_data)
+    dx = s.track_data(i).dx;
+    ldx = sqrt(sum(dx.^2, 2));
+    
+    bValid = ldx > 1e-8;
+    dx = dx(bValid,:);
+    ldx = ldx(bValid);
+    
+    ndx = dx ./ sqrt(ldx);
+    
+    dirc = dot(ndx(1:end-1,:),ndx(2:end,:), 2);
+    na = cross(ndx(1:end-1,:),ndx(2:end,:), 2);
+    
+    nna = na ./ sqrt(sum(na.^2, 2));
+    dsgn = [1; sign(dot(nna(1:end-1,:),nna(2:end,:), 2))];
+    dsgn(dsgn==0) = 1;
+    
+    da{i} = acosd(dirc) .* dsgn;
+end
+
+de = [];
+bEcto = [];
+bDMSO = [];
+
+figure;
+abins = linspace(-180,180,6);
+for i=1:length(da)
+    if ( length(da{i}) < 20 )
+        continue;
+    end
+    
+    bEcto = [bEcto; strcmpi(s.track_data(i).region, 'ecto')];
+    bDMSO = [bDMSO; strcmpi(s.track_data(i).type, 'DMSO')];
+    
+%     n = histcounts(da{i}, abins);
+    hist(da{i});
+end
diff --git a/+Stats/plot_ecto_stats.m b/+Stats/plot_ecto_stats.m
new file mode 100644
index 0000000000000000000000000000000000000000..de36c1418532a650bf06940ffbd772ffb8c7d04b
--- /dev/null
+++ b/+Stats/plot_ecto_stats.m
@@ -0,0 +1,918 @@
+%%
+dmso_order = {'23-06-2016_TimeLapse1_DMSO_20hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse1_Pos2_DMSO_18hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse1_Pos1_DMSO_18hrs_Calcein_FM464';
+            '23-06-2016_TimeLapse2_DMSO_20hrs_Calcein_AzepRH';
+            '28-06-2016_TimeLapse2_DMSO_22hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse1_DMSO_24hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse2_DMSO_24hrs_Calcein_FM464';
+            '22-06-2016_TimeLapse3_DMSO_20hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos1_DMSO_25hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos3_DMSO_25hrs_Calcein_FM464';
+            '28-06-2016_TimeLapse3_Pos4_DMSO_25hrs_Calcein_FM464';
+            '24-06-2016_Timelapse4_DMSO_24hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse3_DMSO_21hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse4_DMSO_21hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse5_DMSO_23hrs_Calcein_FM464'};
+
+axt_order = {'01-07-2016_TimeLapse1_Pos0_Axtinib_150_18hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse1_Pos1_Axtinib_150_18hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse2_Pos2_Axtinib_150_19hrs_Calcein_FM464';
+            '01-07-2016_TimeLapse2_Pos3_Axtinib_150_19hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse1_Axtinib_150_19hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse2_Axtinib_150_20hrs_Calcein_FM464';
+            '24-06-2016_TimeLapse3_Axtinib_150_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos0_Axtinib_150_19hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos1_Axtinib_150_19hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse1_Pos2_Axtinib_150_19hrs_Calcein_FM464';
+            '23-06-2016_Timelapse_3_Axitinib_36hr_Calcein_FM464'};
+
+bb94_order = {'29-06-2016_TimeLapse1_Pos0_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse1_Pos1_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse1_Pos2_BB94_17hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos0_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos1_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse2_Pos2_BB94_21hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos0_BB94_23hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos2_BB94_23hrs_Calcein_FM464';
+            '29-06-2016_TimeLapse3_Pos3_BB94_23hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos0_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos1_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos2_BB94_21hrs_Calcein_FM464';
+            '30-06-2016_TimeLapse2_Pos3_BB94_21hrs_Calcein_FM464'};
+
+all_order = [dmso_order; axt_order; bb94_order];
+
+flist = dir('dataset_ecto_stats-*.mat');
+fnames = {flist.name}.';
+
+load(fnames{end});
+
+%% Sort and only keep matched tracks for now
+matchCells = arrayfun(@(x)(find(strcmpi(all_order,x.name))), ecto_size_stats, 'UniformOutput',false);
+matchTracks = arrayfun(@(x)(find(strcmpi(all_order,x.name))), ecto_track_stats, 'UniformOutput',false);
+
+bMatchedS = cellfun(@(x)(~isempty(x)), matchCells);
+bMatchedT = cellfun(@(x)(~isempty(x)), matchTracks);
+
+chk_size_stats = ecto_size_stats(bMatchedS);
+chk_track_stats = ecto_track_stats(bMatchedT);
+
+[~,invMatch] = sort(vertcat(matchCells{bMatchedS}));
+chk_size_stats = chk_size_stats(invMatch);
+
+[~,invMatch] = sort(vertcat(matchTracks{bMatchedT}));
+chk_track_stats = chk_track_stats(invMatch);
+
+%% Drop empty data
+bValidEctoS = arrayfun(@(x)(~isempty(x.ecto.vsizes)), chk_size_stats);
+bValidSkelS = arrayfun(@(x)(~isempty(x.skel.vsizes)), chk_size_stats);
+
+bValidEctoT = arrayfun(@(x)(~isempty(x.ecto.mean_sizes)), chk_track_stats);
+bValidSkelT = arrayfun(@(x)(~isempty(x.skel.mean_sizes)), chk_track_stats);
+
+bError = (bValidEctoS & ~bValidSkelS);
+err_names = arrayfun(@(x)(x.abrv), chk_size_stats(bError), 'UniformOutput',false);
+if ( any(bError) )
+    fprintf('WARNING: Invalid data detected: No skeletogenic segmentations: (%s)\n', strjoin(err_names,','));
+end
+
+chk_size_stats = chk_size_stats(bValidEctoS);
+
+bError = (bValidEctoT & ~bValidSkelT);
+err_names = arrayfun(@(x)(x.abrv), chk_size_stats(bError), 'UniformOutput',false);
+if ( any(bError) )
+    fprintf('WARNING: Invalid data detected: No skeletogenic segmentations: (%s)\n', strjoin(err_names,','));
+end
+
+chk_track_stats = chk_track_stats(bValidEctoT);
+
+%%
+exp_types = arrayfun(@(x)(regexpi(x.abrv,'(\w*?)_','tokens','once')), chk_size_stats, 'UniformOutput',false);
+exp_types = vertcat(exp_types{:});
+
+ecto_size_info = vertcat(chk_size_stats.ecto);
+skel_size_info = vertcat(chk_size_stats.skel);
+endo_size_info = vertcat(chk_size_stats.endo);
+
+exp_types_track = arrayfun(@(x)(regexpi(x.abrv,'(\w*?)_','tokens','once')), chk_track_stats, 'UniformOutput',false);
+exp_types_track = vertcat(exp_types_track{:});
+
+ecto_track_info = vertcat(chk_track_stats.ecto);
+skel_track_info = vertcat(chk_track_stats.skel);
+endo_track_info = vertcat(chk_track_stats.endo);
+
+%% Interleave ecto/endo and skel
+inter_info = arrayfun(@(x)([x.ecto;x.skel;x.endo]), chk_size_stats, 'UniformOutput',false);
+inter_info = vertcat(inter_info{:});
+
+inter_grp_names = arrayfun(@(x)({[x.abrv '_ecto'];[x.abrv '_skel'];[x.abrv '_ecto']}), chk_size_stats, 'UniformOutput',false);
+inter_grp_names = vertcat(inter_grp_names{:});
+
+inter_sizes = vertcat(inter_info.vsizes);
+inter_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.vsizes,1),1)), inter_grp_names, inter_info, 'UniformOutput',false);
+inter_grp = vertcat(inter_grp_cell{:});
+
+
+%% Make sizes/group names for boxplots
+ecto_sizes = vertcat(ecto_size_info.vsizes);
+skel_sizes = vertcat(skel_size_info.vsizes);
+endo_sizes = vertcat(endo_size_info.vsizes);
+
+ecto_grp_cell = arrayfun(@(x,y)(repmat({x.abrv},size(y.vsizes,1),1)), chk_size_stats, ecto_size_info, 'UniformOutput',false);
+ecto_grp = vertcat(ecto_grp_cell{:});
+
+skel_grp_cell = arrayfun(@(x,y)(repmat({x.abrv},size(y.vsizes,1),1)), chk_size_stats, skel_size_info, 'UniformOutput',false);
+skel_grp = vertcat(skel_grp_cell{:});
+
+endo_grp_cell = arrayfun(@(x,y)(repmat({x.abrv},size(y.vsizes,1),1)), chk_size_stats, endo_size_info, 'UniformOutput',false);
+endo_grp = vertcat(endo_grp_cell{:});
+
+exp_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.vsizes,1),1)), [exp_types;exp_types], [ecto_size_info;skel_size_info], 'UniformOutput',false);
+exp_grp = vertcat(exp_grp_cell{:});
+
+%% Make groups for track stats
+ecto_track_sz = vertcat(ecto_track_info.mean_sizes);
+skel_track_sz = vertcat(skel_track_info.mean_sizes);
+endo_track_sz = vertcat(endo_track_info.mean_sizes);
+
+ecto_grp_cell = arrayfun(@(x,y)(repmat({x.abrv},size(y.mean_sizes,1),1)), chk_track_stats, ecto_track_info, 'UniformOutput',false);
+ecto_track_grp = vertcat(ecto_grp_cell{:});
+
+skel_grp_cell = arrayfun(@(x,y)(repmat({x.abrv},size(y.mean_sizes,1),1)), chk_track_stats, skel_track_info, 'UniformOutput',false);
+skel_track_grp = vertcat(skel_grp_cell{:});
+
+endo_grp_cell = arrayfun(@(x,y)(repmat({x.abrv},size(y.mean_sizes,1),1)), chk_track_stats, endo_track_info, 'UniformOutput',false);
+endo_track_grp = vertcat(endo_grp_cell{:});
+
+exp_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), [exp_types_track;exp_types_track], [ecto_track_info;skel_track_info], 'UniformOutput',false);
+exp_track_grp = vertcat(exp_grp_cell{:});
+
+
+%%
+close all
+
+
+
+% %%
+% figure;
+% boxplot(inter_sizes, inter_grp, 'notch','on');
+% xtickangle(90);
+% zoom('reset');
+% ylim([0.1,1.0]);
+% title('Vesicle size [um^3]');
+% 
+% % draw alternating patches
+% pcol = [0,0,0];
+% palpha = 0.1;
+% pwidth = 2.0;
+% pheight = 1.0;
+% 
+% xstart = 0.5;
+% for i=1:length(inter_info)
+%     if ( mod(i-1,2) == 0 )
+%         pcol = [0,1,0];
+%     else
+%         pcol = [0,0,0];
+%     end
+%     
+%     x0 = xstart;
+%     x1 = xstart + pwidth;
+%     
+%     y0 = 0;
+%     y1 = pheight;
+%     
+%     patch([x0,x1,x1,x0],[y0,y0,y1,y1],pcol, 'FaceColor',pcol, 'FaceAlpha',palpha, 'EdgeColor','none');
+%     
+%     xstart = xstart + pwidth;
+% end
+
+%%
+wdir_grp_names = arrayfun(@(x)(num2str(x+2)), (1:length(ecto_track_info(1).avg_wdir_index{1})).', 'UniformOutput',false);
+
+ecto_wdiridx = [];
+ecto_pl_diridx = [];
+ecto_wdirgrp = {};
+
+ecto_all_didx = vertcat(ecto_track_info.avg_wdir_index);
+ecto_all_plen = vertcat(ecto_track_info.avg_path_wlen);
+ecto_all_mdisp = vertcat(ecto_track_info.avg_max_wdist);
+
+ecto_all_lens = cellfun(@(x)(length(x)), ecto_all_didx);
+for i=1:length(wdir_grp_names)
+    bValid = (ecto_all_lens >= i);
+    
+    didx = cellfun(@(x)(x(i)), ecto_all_didx(bValid));
+    
+    ecto_wdiridx = [ecto_wdiridx; didx];
+    ecto_wdirgrp = [ecto_wdirgrp; repmat(wdir_grp_names(i), size(didx,1),1)];
+end
+
+figure;
+boxplot(ecto_wdiridx,ecto_wdirgrp, 'notch','on');
+
+%%
+didx_idx = 8;
+
+exp_ecto_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, ecto_track_info, 'UniformOutput',false);
+exp_ecto_grp = vertcat(exp_ecto_grp_cell{:});
+
+ecto_wdir_cell = vertcat(ecto_track_info.avg_wdir_index);
+ecto_wdiridx = cellfun(@(x)(x(didx_idx)), ecto_wdir_cell);
+
+bBB94_ecto = cellfun(@(x)(strcmpi(x,'BB94')), exp_ecto_grp);
+exp_ecto_grp = exp_ecto_grp(~bBB94_ecto);
+ecto_wdiridx = ecto_wdiridx(~bBB94_ecto);
+
+figure;
+boxplot(ecto_wdiridx, exp_ecto_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+% ylim([0.3,0.6]);
+title('Directionality Index - Ectoderm');
+
+%
+exp_skel_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, skel_track_info, 'UniformOutput',false);
+exp_skel_grp = vertcat(exp_skel_grp_cell{:});
+
+skel_wdir_cell = vertcat(skel_track_info.avg_wdir_index);
+skel_wdiridx = cellfun(@(x)(x(didx_idx)), skel_wdir_cell);
+
+bBB94_skel = cellfun(@(x)(strcmpi(x,'BB94')), exp_skel_grp);
+exp_skel_grp = exp_skel_grp(~bBB94_skel);
+skel_wdiridx = skel_wdiridx(~bBB94_skel);
+
+figure;
+boxplot(skel_wdiridx, exp_skel_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+% ylim([0.3,0.6]);
+title('Directionality Index - Mesoderm');
+
+%
+exp_endo_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, endo_track_info, 'UniformOutput',false);
+exp_endo_grp = vertcat(exp_endo_grp_cell{:});
+
+endo_wdir_cell = vertcat(endo_track_info.avg_wdir_index);
+endo_wdiridx = cellfun(@(x)(x(didx_idx)), endo_wdir_cell);
+
+bBB94_ecto = cellfun(@(x)(strcmpi(x,'BB94')), exp_endo_grp);
+exp_endo_grp = exp_endo_grp(~bBB94_ecto);
+endo_wdiridx = endo_wdiridx(~bBB94_ecto);
+
+figure;
+boxplot(endo_wdiridx, exp_endo_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+% ylim([0.3,0.6]);
+title('Directionality Index - Endoderm');
+
+%%
+exp_ecto_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, ecto_track_info, 'UniformOutput',false);
+exp_ecto_grp = vertcat(exp_ecto_grp_cell{:});
+
+ecto_msdD = vertcat(ecto_track_info.msd_D);
+ecto_msdR = vertcat(ecto_track_info.msd_R);
+ecto_msdsig = vertcat(ecto_track_info.msd_sig);
+
+bBB94_ecto = cellfun(@(x)(strcmpi(x,'BB94')), exp_ecto_grp);
+exp_ecto_grp = exp_ecto_grp(~bBB94_ecto);
+ecto_msdD = ecto_msdD(~bBB94_ecto);
+ecto_msdR = ecto_msdR(~bBB94_ecto);
+ecto_msdsig = ecto_msdsig(~bBB94_ecto);
+
+figure;
+boxplot(ecto_msdD, exp_ecto_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0,0.04]);
+title('Ideal Diffusion Coefficient [um^2/s] - Ectoderm');
+
+% figure;
+% boxplot(ecto_msdsig, exp_ecto_grp, 'notch','on');
+% xtickangle(30);
+% zoom('reset');
+% ylim([0,2.5]);
+% title('Ideal Diffusion Variance - Ectoderm');
+
+figure;
+hist(ecto_msdR,100);
+title('Ideal Diffusion Fit (Pearson) - Ectoderm')
+
+%
+exp_skel_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, skel_track_info, 'UniformOutput',false);
+exp_skel_grp = vertcat(exp_skel_grp_cell{:});
+
+skel_msdD = vertcat(skel_track_info.msd_D);
+skel_msdR = vertcat(skel_track_info.msd_R);
+skel_msdsig = vertcat(skel_track_info.msd_sig);
+
+bBB94_ecto = cellfun(@(x)(strcmpi(x,'BB94')), exp_skel_grp);
+exp_skel_grp = exp_skel_grp(~bBB94_ecto);
+skel_msdD = skel_msdD(~bBB94_ecto);
+skel_msdR = skel_msdR(~bBB94_ecto);
+skel_msdsig = skel_msdsig(~bBB94_ecto);
+
+figure;
+boxplot(skel_msdD, exp_skel_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0,0.04]);
+title('Ideal Diffusion Coefficient [um^2/s] - Mesoderm');
+
+% figure;
+% boxplot(skel_msdsig, exp_skel_grp, 'notch','on');
+% xtickangle(30);
+% zoom('reset');
+% ylim([0,2.5]);
+% title('Ideal Diffusion Variance - Mesoderm');
+
+figure;
+hist(skel_msdR.^2,100);
+title('Ideal Diffusion Fit (R^2) - Mesoderm')
+
+bDMSO_skel = cellfun(@(x)(strcmpi(x,'DMSO')), exp_skel_grp);
+
+figure;hold on;
+plot(skel_msdD(bDMSO_skel),skel_msdR(bDMSO_skel).^2, '.k');
+plot(skel_msdD(~bDMSO_skel),skel_msdR(~bDMSO_skel).^2, '.r');
+
+test = 1;
+
+% %%
+% didx_idx = 8;
+% 
+% exp_ecto_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, ecto_track_info, 'UniformOutput',false);
+% exp_ecto_grp = vertcat(exp_ecto_grp_cell{:});
+% 
+% ecto_wpathrat_cell = vertcat(ecto_track_info.avg_wpath_ratio);
+% ecto_wpathrat = cellfun(@(x)(x(didx_idx)), ecto_wpathrat_cell);
+% 
+% bBB94_ecto = cellfun(@(x)(strcmpi(x,'BB94')), exp_ecto_grp);
+% exp_ecto_grp = exp_ecto_grp(~bBB94_ecto);
+% ecto_wpathrat = ecto_wpathrat(~bBB94_ecto);
+% 
+% figure;
+% boxplot(ecto_wpathrat, exp_ecto_grp, 'notch','on');
+% xtickangle(30);
+% zoom('reset');
+% % ylim([0.3,0.6]);
+% title('Windowed Path Ratio - Ectoderm');
+% 
+% %
+% exp_skel_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, skel_track_info, 'UniformOutput',false);
+% exp_skel_grp = vertcat(exp_skel_grp_cell{:});
+% 
+% skel_wpathrat_cell = vertcat(skel_track_info.avg_wpath_ratio);
+% skel_wpathrat = cellfun(@(x)(x(didx_idx)), skel_wdir_cell);
+% 
+% bBB94_skel = cellfun(@(x)(strcmpi(x,'BB94')), exp_skel_grp);
+% exp_skel_grp = exp_skel_grp(~bBB94_skel);
+% skel_wpathrat = skel_wpathrat(~bBB94_skel);
+% 
+% figure;
+% boxplot(skel_wpathrat, exp_skel_grp, 'notch','on');
+% xtickangle(30);
+% zoom('reset');
+% % ylim([0.3,0.6]);
+% title('Windowed Path Ratio - Mesoderm');
+
+%%
+exp_ecto_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, ecto_track_info, 'UniformOutput',false);
+exp_ecto_grp = vertcat(exp_ecto_grp_cell{:});
+exp_skel_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, skel_track_info, 'UniformOutput',false);
+exp_skel_grp = vertcat(exp_skel_grp_cell{:});
+
+cmap = lines(4);
+bEctoDMSO = [cellfun(@(x)(strcmpi(x,'DMSO')), exp_ecto_grp); false(size(exp_skel_grp))];
+bEctoAxt = [cellfun(@(x)(strcmpi(x,'Axtinib')), exp_ecto_grp); false(size(exp_skel_grp))];
+bSkelDMSO = [false(size(exp_ecto_grp)); cellfun(@(x)(strcmpi(x,'DMSO')), exp_skel_grp)];
+bSkelAxt = [false(size(exp_ecto_grp)); cellfun(@(x)(strcmpi(x,'Axtinib')), exp_skel_grp)];
+
+didx_idx = 8;
+
+all_mean_sizes = [vertcat(ecto_track_info.mean_sizes); vertcat(skel_track_info.mean_sizes)];
+all_mean_ivel = [vertcat(ecto_track_info.mean_inst_vel); vertcat(skel_track_info.mean_inst_vel)];
+all_diff_coeff = [vertcat(ecto_track_info.msd_D); vertcat(skel_track_info.msd_D)];
+
+all_mean_didx = [vertcat(ecto_track_info.avg_wdir_index); vertcat(skel_track_info.avg_wdir_index)];
+all_mean_didx = cellfun(@(x)(x(didx_idx)), all_mean_didx);
+
+all_mean_mdidx = [vertcat(ecto_track_info.avg_wdir_mindex); vertcat(skel_track_info.avg_wdir_mindex)];
+all_mean_mdidx = cellfun(@(x)(x(didx_idx)), all_mean_mdidx);
+
+all_wpath_ratio = [vertcat(ecto_track_info.avg_wpath_ratio); vertcat(skel_track_info.avg_wpath_ratio)];
+all_wpath_ratio = cellfun(@(x)(x(didx_idx)), all_wpath_ratio);
+
+all_path_len = [vertcat(ecto_track_info.path_len); vertcat(skel_track_info.path_len)];
+all_direct_len = [vertcat(ecto_track_info.direct_len); vertcat(skel_track_info.direct_len)];
+
+all_path_ratio = all_direct_len ./ all_path_len;
+
+%%
+[~,ctr] = hist(all_mean_ivel(bSkelDMSO), ctr);
+figure;
+hist(all_mean_ivel(bSkelDMSO), ctr);
+xlim([0,0.3]);
+title('Avg. Speed [um/s]: Control - Mesoderm');
+saveas(gcf, 'C:/Users/mwinter/Desktop/figs/speed_hist_control_mesoderm.png','png');
+%
+figure;
+hist(all_mean_ivel(bSkelAxt), ctr);
+xlim([0,0.3]);
+title('Avg. Speed [um/s]: VEGFR Inhib. - Mesoderm');
+saveas(gcf, 'C:/Users/mwinter/Desktop/figs/speed_hist_axt_mesoderm.png','png');
+%
+figure;
+hist(all_mean_ivel(bEctoDMSO), ctr);
+xlim([0,0.3]);
+title('Avg. Speed [um/s]: Control - Ectoderm');
+saveas(gcf, 'C:/Users/mwinter/Desktop/figs/speed_hist_control_ectoderm.png','png');
+%
+figure;
+hist(all_mean_ivel(bEctoAxt), ctr);
+xlim([0,0.3]);
+title('Avg. Speed [um/s]: VEGFR Inhib. - Ectoderm');
+saveas(gcf, 'C:/Users/mwinter/Desktop/figs/speed_hist_axt_ectoderm.png','png');
+
+% %%
+% figure;
+% hist(log(all_mean_ivel(bSkelDMSO)), 25);
+% title('Log-Avg. Speed: Control - Mesoderm');
+% 
+% figure;
+% hist(log(all_mean_ivel(bEctoDMSO)), 25);
+% title('Log-Avg. Speed: Control - Ectoderm');
+% 
+% figure;
+% hist(log(all_mean_ivel(bSkelAxt)), 25);
+% title('Log-Avg. Speed: VEGFR Inhib. - Mesoderm');
+% 
+% figure;
+% hist(log(all_mean_ivel(bEctoAxt)), 25);
+% title('Log-Avg. Speed: VEGFR Inhib. - Ectoderm');
+
+% %%
+% figure;
+% hist(log(all_mean_ivel(bEctoDMSO)), 25);
+% title('Log-Avg. Speed [um/s]: VEGFR Inhib. - Ectoderm');
+
+%% Plot size vs. average instantaneous speed
+figure;hold on;
+plot(all_mean_sizes(bSkelDMSO),all_mean_ivel(bSkelDMSO), '.', 'Color',cmap(2,:));
+% title('Avg. Speed [um/s] vs. Avg. Size [um]');
+xlim([0.1,1.3]);
+ylim([0,0.2]);
+%
+plot(all_mean_sizes(bSkelAxt),all_mean_ivel(bSkelAxt), '.', 'Color',cmap(1,:));
+
+[b,~,r,~,rst] = regress(all_mean_ivel(bSkelAxt|bSkelDMSO), [ones(size(all_mean_ivel(bSkelAxt|bSkelDMSO))), all_mean_sizes(bSkelAxt|bSkelDMSO)]);
+xx = 0:0.1:1.3;
+yy = b(1) + xx*b(2);
+plot(xx,yy, '-k', 'LineWidth',2);
+legend('Mesoderm: Control','Mesoderm: VEGFR Inhib.');
+saveas(gcf,'C:\Users\mwinter\Desktop\figs\sup2_meso_speed_v_size.png');
+
+% figure;plot(all_mean_sizes(bEctoDMSO|bSkelDMSO), r, '.r');
+%%
+figure;hold on;
+plot(all_mean_sizes(bEctoDMSO),all_mean_ivel(bEctoDMSO), '.', 'Color',cmap(2,:));
+% title('Avg. Speed [um/s] vs. Avg. Size [um]');
+xlim([0.1,1.3]);
+ylim([0,0.2]);
+%
+plot(all_mean_sizes(bEctoAxt),all_mean_ivel(bEctoAxt), '.', 'Color',cmap(1,:));
+
+[b,~,r,~,rst] = regress(all_mean_ivel(bEctoAxt|bEctoDMSO), [ones(size(all_mean_ivel(bEctoAxt|bEctoDMSO))), all_mean_sizes(bEctoAxt|bEctoDMSO)]);
+xx = 0:0.1:1.3;
+yy = b(1) + xx*b(2);
+plot(xx,yy, '-k', 'LineWidth',2);
+legend('Ectoderm: Control','Ectoderm: VEGFR Inhib.');
+saveas(gcf,'C:\Users\mwinter\Desktop\figs\sup2_ecto_speed_v_size.png');
+
+
+%% Mean size vs. diffusion coeff
+figure;hold on;
+plot(all_mean_sizes(bSkelDMSO),all_diff_coeff(bSkelDMSO), '.', 'Color',cmap(2,:));
+% title('Avg. Speed [um/s] vs. Avg. Size [um]');
+xlim([0.1,1.3]);
+ylim([0,0.05]);
+%
+plot(all_mean_sizes(bSkelAxt),all_diff_coeff(bSkelAxt), '.', 'Color',cmap(1,:));
+
+[b,~,r,~,rst] = regress(all_diff_coeff(bSkelAxt|bSkelDMSO), [ones(size(all_diff_coeff(bSkelAxt|bSkelDMSO))), all_mean_sizes(bSkelAxt|bSkelDMSO)]);
+xx = 0:0.1:1.3;
+yy = b(1) + xx*b(2);
+plot(xx,yy, '-k', 'LineWidth',2);
+legend('Mesoderm: Control','Mesoderm: VEGFR Inhib.');
+saveas(gcf,'C:\Users\mwinter\Desktop\figs\sup3_meso_diff_v_size.png');
+
+% figure;plot(all_mean_sizes(bEctoDMSO|bSkelDMSO), r, '.r');
+%%
+figure;hold on;
+plot(all_mean_sizes(bEctoDMSO),all_diff_coeff(bEctoDMSO), '.', 'Color',cmap(2,:));
+% title('Avg. Speed [um/s] vs. Avg. Size [um]');
+xlim([0.1,1.3]);
+ylim([0,0.05]);
+%
+plot(all_mean_sizes(bEctoAxt),all_diff_coeff(bEctoAxt), '.', 'Color',cmap(1,:));
+
+[b,~,r,~,rst] = regress(all_diff_coeff(bEctoAxt|bEctoDMSO), [ones(size(all_diff_coeff(bEctoAxt|bEctoDMSO))), all_mean_sizes(bEctoAxt|bEctoDMSO)]);
+xx = 0:0.1:1.3;
+yy = b(1) + xx*b(2);
+plot(xx,yy, '-k', 'LineWidth',2);
+legend('Ectoderm: Control','Ectoderm: VEGFR Inhib.');
+saveas(gcf,'C:\Users\mwinter\Desktop\figs\sup3_ecto_diff_v_size.png');
+
+
+
+%%
+figure;hold on;
+plot(all_mean_sizes(bEctoDMSO),all_path_ratio(bEctoDMSO), '.', 'Color',cmap(1,:));
+plot(all_mean_sizes(bEctoAxt),all_path_ratio(bEctoAxt), '.', 'Color',cmap(2,:));
+plot(all_mean_sizes(bSkelDMSO),all_path_ratio(bSkelDMSO), '.', 'Color',cmap(3,:));
+plot(all_mean_sizes(bSkelAxt),all_path_ratio(bSkelAxt), '.', 'Color',cmap(4,:));
+legend('DMSO - Ecto','Axtinib - Ecto','DMSO - Meso','Axtinib - Meso');
+title('Path Ratio vs. Avg. Size [um]');
+xlim([0.1,1.3]);
+
+%%
+figure;hold on;
+plot(all_mean_sizes(bEctoDMSO),all_mean_didx(bEctoDMSO), '.', 'Color',cmap(1,:));
+plot(all_mean_sizes(bEctoAxt),all_mean_didx(bEctoAxt), '.', 'Color',cmap(2,:));
+plot(all_mean_sizes(bSkelDMSO),all_mean_didx(bSkelDMSO), '.', 'Color',cmap(3,:));
+plot(all_mean_sizes(bSkelAxt),all_mean_didx(bSkelAxt), '.', 'Color',cmap(4,:));
+legend('DMSO - Ecto','Axtinib - Ecto','DMSO - Meso','Axtinib - Meso');
+title('Directed Index vs. Avg. Size [um]');
+xlim([0.1,1.3]);
+
+% %%
+% figure;hold on;
+% plot(all_mean_sizes(bEctoDMSO),all_mean_mdidx(bEctoDMSO), '.', 'Color',cmap(1,:));
+% plot(all_mean_sizes(bEctoAxt),all_mean_mdidx(bEctoAxt), '.', 'Color',cmap(2,:));
+% plot(all_mean_sizes(bSkelDMSO),all_mean_mdidx(bSkelDMSO), '.', 'Color',cmap(3,:));
+% plot(all_mean_sizes(bSkelAxt),all_mean_mdidx(bSkelAxt), '.', 'Color',cmap(4,:));
+% legend('DMSO - Ecto','Axtinib - Ecto','DMSO - Meso','Axtinib - Meso');
+% title('Directed Index (MeanDisp) vs. Avg. Size [um]');
+% xlim([0.1,1.3]);
+
+%%
+figure;hold on;
+plot(all_mean_sizes(bEctoDMSO),all_wpath_ratio(bEctoDMSO), '.', 'Color',cmap(1,:));
+plot(all_mean_sizes(bEctoAxt),all_wpath_ratio(bEctoAxt), '.', 'Color',cmap(2,:));
+plot(all_mean_sizes(bSkelDMSO),all_wpath_ratio(bSkelDMSO), '.', 'Color',cmap(3,:));
+plot(all_mean_sizes(bSkelAxt),all_wpath_ratio(bSkelAxt), '.', 'Color',cmap(4,:));
+legend('DMSO - Ecto','Axtinib - Ecto','DMSO - Meso','Axtinib - Meso');
+title('Avg. Windowed Path Ratio vs. Avg. Size [um]');
+xlim([0.1,1.3]);
+
+
+test = 1;
+
+%%
+exp_ecto_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.vsizes,1),1)), exp_types, ecto_size_info, 'UniformOutput',false);
+exp_ecto_grp = vertcat(exp_ecto_grp_cell{:});
+
+figure;
+boxplot(ecto_sizes, exp_ecto_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,1.0]);
+title('Vesicle Size - Ectoderm (N=3057,7663,6050) [um^3]');
+
+%
+exp_skel_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.vsizes,1),1)), exp_types, skel_size_info, 'UniformOutput',false);
+exp_skel_grp = vertcat(exp_skel_grp_cell{:});
+
+figure;
+boxplot(skel_sizes, exp_skel_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,1.0]);
+title('Vesicle Size - Mesoderm (N=1443,2125,1832) [um^3]');
+
+%
+all_sizes = [ecto_sizes;skel_sizes];
+
+exp_all_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.vsizes,1),1)), [exp_types;exp_types], [ecto_size_info;skel_size_info], 'UniformOutput',false);
+exp_all_grp = vertcat(exp_all_grp_cell{:});
+
+figure;
+boxplot(all_sizes, exp_all_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,1.0]);
+title('Vesicle Size - All (N=4500,9788,7882) [um^3]');
+
+%%
+figure;
+boxplot(skel_sizes, skel_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.1,1.0]);
+title('Vesicle size (mesoderm) [um^3]');
+
+%%
+figure;
+boxplot(ecto_sizes, ecto_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.1,1.0]);
+title('Vesicle size (ectoderm) [um^3]');
+
+%%
+figure;
+stem(vertcat(skel_size_info.count),'k');
+set(gca, 'TickLabelInterpreter','none');
+xticks(1:length(chk_size_stats));
+xticklabels('manual');
+xticklabels({chk_size_stats.abrv});
+xtickangle(30)
+title('Vesicle counts (mesoderm) [um^3]');
+
+%%
+figure;
+stem(vertcat(skel_size_info.count)./vertcat(ecto_size_info.rsize),'k');
+set(gca, 'TickLabelInterpreter','none');
+xticks(1:length(chk_size_stats));
+xticklabels('manual');
+xticklabels({chk_size_stats.abrv});
+xtickangle(30)
+title('Volume-normalized vesicle counts (mesoderm)');
+
+%%
+figure;
+stem(vertcat(ecto_size_info.count),'k');
+set(gca, 'TickLabelInterpreter','none');
+xticks(1:length(chk_size_stats));
+xticklabels('manual');
+xticklabels({chk_size_stats.abrv});
+xtickangle(30)
+title('Vesicle counts (ectoderm) [um^3]');
+
+%%
+figure;
+stem(vertcat(ecto_size_info.count)./vertcat(ecto_size_info.rsize),'k');
+set(gca, 'TickLabelInterpreter','none');
+xticks(1:length(chk_size_stats));
+xticklabels('manual');
+xticklabels({chk_size_stats.abrv});
+xtickangle(30)
+title('Volume-normalized vesicle counts (ectoderm)');
+
+% %%
+% figure;
+% stem(vertcat(ecto_info.rsize),'k');
+% set(gca, 'TickLabelInterpreter','none');
+% xticks(1:length(chk_size_stats));
+% xticklabels('manual');
+% xticklabels({chk_size_stats.abrv});
+% xtickangle(30)
+% title('Volume (ectoderm) [um^3]');
+
+% %%
+% figure;
+% stem(vertcat(skel_size_info.count)./vertcat(ecto_size_info.count),'k');
+% set(gca, 'TickLabelInterpreter','none');
+% xticks(1:length(chk_size_stats));
+% xticklabels('manual');
+% xticklabels({chk_size_stats.abrv});
+% xtickangle(30)
+% title('Vesicle count ratio (mesoderm/ectoderm)');
+% 
+% %%
+% figure;
+% boxplot(vertcat(skel_size_info.count)./vertcat(ecto_size_info.count),exp_types);
+% xtickangle(30)
+% title('Vesicle count ratio (mesoderm/ectoderm)');
+
+% %%
+% ecto_vol = arrayfun(@(x)(sum(x.vsizes)), ecto_size_info);
+% skel_vol = arrayfun(@(x)(sum(x.vsizes)), skel_size_info);
+% 
+% figure;
+% boxplot(skel_vol ./ ecto_vol, exp_types);
+% xtickangle(30)
+% title('Vesicle volume ratio (mesoderm/ectoderm)');
+% 
+% %%
+% figure;
+% boxplot(skel_vol, exp_types);
+% xtickangle(30)
+% title('Vesicle volume (mesoderm)');
+% 
+% %%
+% figure;
+% boxplot(ecto_vol, exp_types);
+% xtickangle(30)
+% title('Vesicle volume (ectoderm)');
+% 
+% %%
+% figure;
+% boxplot(vertcat(ecto_size_info.rsize), exp_types);
+% xtickangle(30)
+% title('Ectoderm volume');
+
+% %%
+% figure;
+% stem(vertcat(skel_info.rsize)./vertcat(ecto_info.rsize),'k');
+% set(gca, 'TickLabelInterpreter','none');
+% xticks(1:length(chk_size_stats));
+% xticklabels('manual');
+% xticklabels({chk_size_stats.abrv});
+% xtickangle(30)
+% title('Volume ratio (mesoderm/ectoderm)');
+% 
+% %%
+% figure;
+% stem((vertcat(skel_size_info.count)./vertcat(ecto_size_info.count)) ./ (vertcat(skel_size_info.rsize)./vertcat(ecto_size_info.rsize)),'k');
+% set(gca, 'TickLabelInterpreter','none');
+% xticks(1:length(chk_size_stats));
+% xticklabels('manual');
+% xticklabels({chk_size_stats.abrv});
+% xtickangle(30)
+% title('Count per volume ratio (mesoderm/ectoderm)');
+
+%% Vesicle path ratio
+%%
+skel_path_rat = vertcat(skel_track_info.direct_len) ./ vertcat(skel_track_info.path_len);
+
+figure;
+boxplot(skel_path_rat, skel_track_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.1,1.0]);
+title('Vesicle path ratio (mesoderm)');
+
+%%
+ecto_path_rat = vertcat(ecto_track_info.direct_len) ./ vertcat(ecto_track_info.path_len);
+
+figure;
+boxplot(ecto_path_rat, ecto_track_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.1,1.0]);
+title('Vesicle path ratio (ectoderm)');
+
+%% Vesicle path ratio -- bulk groups
+%%
+exp_ecto_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, ecto_track_info, 'UniformOutput',false);
+exp_ecto_grp = vertcat(exp_ecto_grp_cell{:});
+
+bBB94_ecto = cellfun(@(x)(strcmpi(x,'BB94')), exp_ecto_grp);
+exp_ecto_grp = exp_ecto_grp(~bBB94_ecto);
+ecto_path_rat = ecto_path_rat(~bBB94_ecto);
+
+figure;
+boxplot(ecto_path_rat, exp_ecto_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,1.0]);
+title('Path Length Ratio - Ectoderm (N=5539,7369)');
+
+%
+exp_skel_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, skel_track_info, 'UniformOutput',false);
+exp_skel_grp = vertcat(exp_skel_grp_cell{:});
+
+bBB94_skel = cellfun(@(x)(strcmpi(x,'BB94')), exp_skel_grp);
+exp_skel_grp = exp_skel_grp(~bBB94_skel);
+skel_path_rat = skel_path_rat(~bBB94_skel);
+
+figure;
+boxplot(skel_path_rat, exp_skel_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,1.0]);
+title('Path Length Ratio - Mesoderm (N=2606,2096)');
+
+%
+all_path_rat = [vertcat(ecto_track_info.direct_len);vertcat(skel_track_info.direct_len)] ./ [vertcat(ecto_track_info.path_len);vertcat(skel_track_info.path_len)];
+
+exp_all_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), [exp_types_track;exp_types_track], [ecto_track_info;skel_track_info], 'UniformOutput',false);
+exp_all_grp = vertcat(exp_all_grp_cell{:});
+
+bBB94_all = cellfun(@(x)(strcmpi(x,'BB94')), exp_all_grp);
+exp_all_grp = exp_all_grp(~bBB94_all);
+all_path_rat = all_path_rat(~bBB94_all);
+
+figure;
+boxplot(all_path_rat, exp_all_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,1.0]);
+title('Path Length Ratio - All (N=8145,9465)');
+
+%% Instantaneous Velocity -- bulk groups
+%% 
+exp_ecto_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, ecto_track_info, 'UniformOutput',false);
+exp_ecto_grp = vertcat(exp_ecto_grp_cell{:});
+
+ecto_inst_vel = vertcat(ecto_track_info.mean_inst_vel);
+
+bBB94_ecto = cellfun(@(x)(strcmpi(x,'BB94')), exp_ecto_grp);
+exp_ecto_grp = exp_ecto_grp(~bBB94_ecto);
+ecto_inst_vel = ecto_inst_vel(~bBB94_ecto);
+
+figure;
+boxplot(ecto_inst_vel, exp_ecto_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,0.2]);
+title('Instantaneous Velocity - Ectoderm (N=5539,7369)[um/s]');
+
+%
+exp_skel_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, skel_track_info, 'UniformOutput',false);
+exp_skel_grp = vertcat(exp_skel_grp_cell{:});
+
+skel_inst_vel = vertcat(skel_track_info.mean_inst_vel);
+
+bBB94_skel = cellfun(@(x)(strcmpi(x,'BB94')), exp_skel_grp);
+exp_skel_grp = exp_skel_grp(~bBB94_skel);
+skel_inst_vel = skel_inst_vel(~bBB94_skel);
+
+figure;
+boxplot(skel_inst_vel, exp_skel_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,0.2]);
+title('Instantaneous Velocity - Mesoderm (N=2606,2096)[um/s]');
+
+%
+all_inst_vel = [vertcat(ecto_track_info.mean_inst_vel); vertcat(skel_track_info.mean_inst_vel)];
+
+exp_all_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), [exp_types_track;exp_types_track], [ecto_track_info;skel_track_info], 'UniformOutput',false);
+exp_all_grp = vertcat(exp_all_grp_cell{:});
+
+bBB94_all = cellfun(@(x)(strcmpi(x,'BB94')), exp_all_grp);
+exp_all_grp = exp_all_grp(~bBB94_all);
+all_inst_vel = all_inst_vel(~bBB94_all);
+
+figure;
+boxplot(all_inst_vel, exp_all_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,0.2]);
+title('Instantaneous Velocity - All (N=8145,9465)[um/s]');
+
+
+%%
+all_inst_vel = [vertcat(ecto_track_info.mean_inst_vel); vertcat(skel_track_info.mean_inst_vel)];
+
+figure;
+boxplot(all_inst_vel, [ecto_track_grp;skel_track_grp], 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,0.15]);
+title('Vesicle average instantaneous velocity (all) [um/sec]');
+
+%% Instantaneous velocity comparison
+%%
+exp_ecto_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, ecto_track_info, 'UniformOutput',false);
+exp_ecto_grp = vertcat(exp_ecto_grp_cell{:});
+
+figure;
+boxplot(vertcat(ecto_track_info.mean_inst_vel), ecto_track_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,0.15]);
+title('Vesicle average instantaneous velocity (ectoderm) [um/sec]');
+
+%
+exp_skel_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, skel_track_info, 'UniformOutput',false);
+exp_skel_grp = vertcat(exp_skel_grp_cell{:});
+
+figure;
+boxplot(vertcat(skel_track_info.mean_inst_vel), skel_track_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.0,0.15]);
+title('Vesicle average instantaneous velocity (mesoderm) [um/sec]');
+
+
+%% Average instantaneous velocity
+%%
+exp_grp_cell = arrayfun(@(x,y)(repmat(x,size(y.mean_sizes,1),1)), exp_types_track, skel_track_info, 'UniformOutput',false);
+exp_grp = vertcat(exp_skel_grp_cell{:});
+
+
+%% Vesicle brownian variance
+%%
+skel_brn_var = vertcat(skel_track_info.brownian_var);
+
+figure;
+boxplot(skel_brn_var, skel_track_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.1,1.0]);
+title('Vesicle brownian variance (mesoderm)');
+
+%%
+ecto_brn_var = vertcat(ecto_track_info.brownian_var);
+
+figure;
+boxplot(ecto_brn_var, ecto_track_grp, 'notch','on');
+xtickangle(30);
+zoom('reset');
+ylim([0.1,1.0]);
+title('Vesicle brownian variance (ectoderm)');
+
diff --git a/+Stats/plot_motion_size.m b/+Stats/plot_motion_size.m
new file mode 100644
index 0000000000000000000000000000000000000000..84bd69a80e034b6e27291e18538c1055bba0941a
--- /dev/null
+++ b/+Stats/plot_motion_size.m
@@ -0,0 +1,100 @@
+flist = dir('dataset_collate_tracks-*.mat');
+fnames = {flist.name}.';
+
+s = load(fnames{end});
+
+% figure;hold on;
+% for i=1:length(s.track_data)
+%     szs = s.track_data(i).sizes;
+%     h = ones(5,1);
+%     h = h / sum(h);
+%     
+%     smsz = conv(szs, h, 'same');
+%     nrm = conv(ones(size(szs,1),1), h, 'same');
+%     
+%     smsz = smsz ./ nrm;
+%     plot(smsz, '-k');
+% end
+
+window_size = 2*60;
+
+
+avg_length = 15;
+
+bDMSO = strcmpi({s.track_data.type}.', 'DMSO');
+bSkel = strcmpi({s.track_data.region}.', 'skel');
+
+s.track_data = s.track_data(bDMSO);
+
+bSlowing = false(length(s.track_data),1);
+slowSz = zeros(length(s.track_data),1);
+for i=1:length(s.track_data)
+    if ( length(s.track_data(i).times) < avg_length )
+        continue;
+    end
+    
+    mlen = min(avg_length, length(s.track_data(i).msd)-2);
+    
+    bIdx = 1:mlen;
+    eIdx = (length(s.track_data(i).msd)-mlen+1):length(s.track_data(i).msd);
+    
+    avgBeg = mean(sqrt(s.track_data(i).msd(bIdx)) ./ s.track_data(i).dt(bIdx));
+    avgEnd = mean(sqrt(s.track_data(i).msd(eIdx)) ./ s.track_data(i).dt(eIdx));
+    
+    bSlowing(i) = (avgEnd <= 0.5*avgBeg);
+    slowSz(i) = avgEnd-avgBeg;
+end
+
+bShrinking = false(length(s.track_data),1);
+shrinkSz = zeros(length(s.track_data),1);
+for i=1:length(s.track_data)
+    mlen = min(avg_length, length(s.track_data(i).sizes)-2);
+    
+    bIdx = 1:mlen;
+    eIdx = (length(s.track_data(i).sizes)-mlen+1):length(s.track_data(i).sizes);
+    
+    avgBeg = mean(s.track_data(i).sizes(bIdx));
+    avgEnd = mean(s.track_data(i).sizes(eIdx));
+    
+    bShrinking(i) = (avgEnd <= 0.5*avgBeg);
+    shrinkSz(i) = avgEnd-avgBeg;
+end
+
+figure;hist(shrinkSz(bShrinking&bSlowing),25);
+figure;hist(slowSz(bShrinking&bSlowing),25);
+
+cmap = lines(10);
+
+figure;hold on;
+shsl = s.track_data(bShrinking&bSlowing);
+for i=1:length(shsl)
+    fprintf('%s:%d\n', shsl(i).name, shsl(i).tid);
+    mlen = min(avg_length, length(shsl(i).msd)-2);
+    
+    ivels = sqrt(shsl(i).msd) ./ shsl(i).dt;
+    h = ones(mlen,1);
+    h = h / sum(h);
+    
+    smvel = conv(ivels, h, 'same');
+    nrm = conv(ones(size(ivels,1),1), h, 'same');
+    
+    smvel = smvel ./ nrm;
+    plot(smvel, '-', 'Color',cmap(mod(i-1,10)+1,:));
+    title('Smoothed instantaneous speed');
+end
+
+figure;hold on;
+for i=1:length(shsl)
+    mlen = min(avg_length, length(shsl(i).sizes)-2);
+    
+    isz = shsl(i).sizes;
+    h = ones(mlen,1);
+    h = h / sum(h);
+    
+    smsz = conv(isz, h, 'same');
+    nrm = conv(ones(size(isz,1),1), h, 'same');
+    
+    smsz = smsz ./ nrm;
+    plot(smsz, '-', 'Color',cmap(mod(i-1,10)+1,:));
+    title('Smoothed size');
+end
diff --git a/+Stats/plot_vel_histograms.m b/+Stats/plot_vel_histograms.m
new file mode 100644
index 0000000000000000000000000000000000000000..5d0dcc0661e43641befe03854b74fcd5642b79ac
--- /dev/null
+++ b/+Stats/plot_vel_histograms.m
@@ -0,0 +1,89 @@
+flist = dir('dataset_collate_tracks-*.mat');
+fnames = {flist.name}.';
+
+s = load(fnames{end});
+
+bSkel = arrayfun(@(x)(strcmpi(x.region,'skel')), s.track_data);
+bEcto = arrayfun(@(x)(strcmpi(x.region,'ecto')), s.track_data);
+
+bDMSO = arrayfun(@(x)(strcmpi(x.type,'DMSO')), s.track_data);
+bAxt = arrayfun(@(x)(strcmpi(x.type,'Axtinib')), s.track_data);
+
+dmso_skel_data = s.track_data(bDMSO&bSkel);
+dmso_ecto_data = s.track_data(bDMSO&bEcto);
+axt_skel_data = s.track_data(bAxt&bSkel);
+axt_ecto_data = s.track_data(bAxt&bEcto);
+
+dmso_skel_ivel_c = arrayfun(@(x)(sum(x.dx.^2,2) ./ x.dt), dmso_skel_data, 'UniformOutput',false);
+dmso_ecto_ivel_c = arrayfun(@(x)(sum(x.dx.^2,2) ./ x.dt), dmso_ecto_data, 'UniformOutput',false);
+axt_skel_ivel_c = arrayfun(@(x)(sum(x.dx.^2,2) ./ x.dt), axt_skel_data, 'UniformOutput',false);
+axt_ecto_ivel_c = arrayfun(@(x)(sum(x.dx.^2,2) ./ x.dt), axt_ecto_data, 'UniformOutput',false);
+
+dmso_skel_ivel = vertcat(dmso_skel_ivel_c{:});
+dmso_ecto_ivel = vertcat(dmso_ecto_ivel_c{:});
+axt_skel_ivel = vertcat(axt_skel_ivel_c{:});
+axt_ecto_ivel = vertcat(axt_ecto_ivel_c{:});
+
+nbins = 100;
+
+figure;
+subplot(2,2,1)
+hist((dmso_skel_ivel), 100*nbins);
+xlim([0,0.1]);
+title('Instantaneous Velocities - Control - Mesoderm')
+
+subplot(2,2,2)
+hist((dmso_ecto_ivel), 100*nbins);
+xlim([0,0.1]);
+title('Instantaneous Velocities - Control - Ectoderm')
+
+subplot(2,2,3)
+hist((axt_skel_ivel), 100*nbins);
+xlim([0,0.1]);
+title('Instantaneous Velocities - VEGFR Inhib. - Mesoderm')
+
+subplot(2,2,4)
+hist((axt_ecto_ivel), 100*nbins);
+xlim([0,0.1]);
+title('Instantaneous Velocities - VEGFR Inhib. - Ectoderm')
+
+
+%%
+figure;
+%%
+subplot(2,2,1)
+hist(log10(dmso_skel_ivel), nbins);
+oldL = get(gca, 'XTickLabel');
+newL = cellfun(@(x)(['10^{' x '}']), oldL,'UniformOutput',false);
+set(gca, 'XTickLabel',newL);
+title('Instantaneous Speed - Control - Mesoderm')
+xlabel('Vesicle Speed [\mum/s]')
+
+subplot(2,2,2)
+hist(log10(dmso_ecto_ivel), nbins);
+oldL = get(gca, 'XTickLabel');
+newL = cellfun(@(x)(['10^{' x '}']), oldL,'UniformOutput',false);
+set(gca, 'XTickLabel',newL);
+title('Instantaneous Speed - Control - Ectoderm')
+xlabel('Vesicle Speed [\mum/s]')
+
+subplot(2,2,3)
+hist(log10(axt_skel_ivel), nbins);
+hist(log10(dmso_ecto_ivel), nbins);
+oldL = get(gca, 'XTickLabel');
+newL = cellfun(@(x)(['10^{' x '}']), oldL,'UniformOutput',false);
+set(gca, 'XTickLabel',newL);
+title('Instantaneous Speed - VEGFR Inhib. - Mesoderm')
+xlabel('Vesicle Speed [\mum/s]')
+
+subplot(2,2,4)
+hist(log10(axt_ecto_ivel), nbins);
+hist(log10(dmso_ecto_ivel), nbins);
+oldL = get(gca, 'XTickLabel');
+newL = cellfun(@(x)(['10^{' x '}']), oldL,'UniformOutput',false);
+set(gca, 'XTickLabel',newL);
+title('Instantaneous Speed - VEGFR Inhib. - Ectoderm')
+xlabel('Vesicle Speed [\mum/s]')
+
+saveas(gcf, 'C:\Users\mwinter\Desktop\figs\sup_figure2.png');
+
diff --git a/+Stats/write_compare.m b/+Stats/write_compare.m
new file mode 100644
index 0000000000000000000000000000000000000000..1d603d1e72f10b65dc706e72a2f68286603d73b0
--- /dev/null
+++ b/+Stats/write_compare.m
@@ -0,0 +1,55 @@
+function tbl = write_compare(fid, stats_tbl, sum_tbl, vname, title, types)
+    sgrp = cellfun(@(x,y)([x ':' y]), stats_tbl.region, stats_tbl.type, 'UniformOutput',false);
+    [p,tbl,stats] = kruskalwallis(stats_tbl.(vname), sgrp, 'off');
+    [c,~,~,gn] = multcompare(stats, 'CType','dunn-sidak', 'Display','off');
+    
+    m = size(c,1);
+    pt = zeros(m,1);
+    for i=1:m
+        bga = strcmp(sgrp, gn{c(i,1)});
+        bgb = strcmp(sgrp, gn{c(i,2)});
+        
+        pt(i) = 1 - (1-ranksum(stats_tbl.(vname)(bga),stats_tbl.(vname)(bgb)))^m;
+    end
+
+    gt = {};
+    midx = [];
+
+    %% Assumed regions/types (to get proper sorting)
+    regions = {'Mesoderm';'Ectoderm'};
+%     types = {'Control';'VEGFR Inhibition'};
+    for i=1:length(regions)
+        for j=1:(length(types)-1)
+            t2 = types((j+1):end);
+            np = cellfun(@(x)({[regions{i} ':' types{j}], [regions{i} ':' x]}), t2, 'UniformOutput',false);
+            
+            gt = [gt; vertcat(np{:})];
+        end
+    end
+
+    for i=1:length(types)
+        for j=1:(length(regions)-1)
+            r2 = regions((j+1):end);
+            np = cellfun(@(x)({[regions{j} ':' types{i}], [x ':' types{i}]}), r2, 'UniformOutput',false);
+            
+            gt = [gt; vertcat(np{:})];
+        end
+    end
+
+    fprintf(fid, '%s\n', title);
+    fprintf(fid, 'Kruskal-Wallis, %g\n\n', p);
+    
+%     fprintf(fid, '\n');
+    fprintf(fid, 'Pair-wise (Dunn-Sidak), Difference, P-Value\n');
+    for i=1:size(gt,1)
+        ia = find(strcmpi(gn,gt{i,1}));
+        ib = find(strcmpi(gn,gt{i,2}));
+        
+        ma = sum_tbl.median(ia);
+        mb = sum_tbl.median(ib);
+        
+        bridx = all((c(:,1:2)==[ia,ib]), 2);
+        fprintf(fid, '%s to %s,%f,%g\n', gt{i,1},gt{i,2}, mb-ma, c(bridx,6));
+    end
+    fprintf(fid, '\n\n');
+end
diff --git a/+Stats/write_compare_spicule.m b/+Stats/write_compare_spicule.m
new file mode 100644
index 0000000000000000000000000000000000000000..41a834a1077bbc2e443ebcaa7ce95962a30f7a77
--- /dev/null
+++ b/+Stats/write_compare_spicule.m
@@ -0,0 +1,32 @@
+function [pKW,pM] = write_compare_spicule(fid, spc_tbl, sum_tbl, fieldname, cmp_title)
+    [pKW,~,stats] = kruskalwallis(spc_tbl.(fieldname), spc_tbl.sp_dist,'off');
+    [c,~,~,gnames] = multcompare(stats, 'CType','dunn-sidak', 'Display','off');
+    
+    pM = ones(length(gnames),length(gnames));
+    for i=1:size(c,1)
+        pM(c(i,1),c(i,2)) = c(i,6);
+        pM(c(i,2),c(i,1)) = c(i,6);
+    end
+    
+    fprintf(fid, '%s\n', cmp_title);
+    fprintf(fid, 'Kruskal-Wallis, %g\n\n', pKW);
+    
+    fprintf(fid, 'Pair-Wise Difference\n');
+    fprintf(fid, 'Median (Col - Row),%s\n', strjoin(gnames,','));
+    for i=1:size(gnames,1)
+        diff_row = (sum_tbl.median - sum_tbl.median(i));
+        
+        nstr = arrayfun(@(x)(num2str(x,'%f')), diff_row, 'UniformOutput',false);
+        fprintf(fid, '%s,%s\n', gnames{i}, strjoin(nstr,','));
+    end
+    fprintf(fid, '\n\n');
+    
+    
+    fprintf(fid, 'Pair-wise (Dunn-Sidak)\n');
+    fprintf(fid, 'P-Value,%s\n', strjoin(gnames,','));
+    for i=1:size(gnames,1)
+        nstr = arrayfun(@(x)(num2str(x,'%f')), pM(i,:), 'UniformOutput',false);
+        fprintf(fid, '%s,%s\n', gnames{i}, strjoin(nstr,','));
+    end
+    fprintf(fid, '\n\n');
+end
diff --git a/+Stats/write_summary.m b/+Stats/write_summary.m
new file mode 100644
index 0000000000000000000000000000000000000000..07aa6aa0c322f852c3461bc0eb7059bceee96575
--- /dev/null
+++ b/+Stats/write_summary.m
@@ -0,0 +1,40 @@
+function stbl = write_summary(fid, stats_tbl, vname, title, types)
+    %% Assumed regions/types (to get proper sorting)
+    regions = {'Mesoderm';'Ectoderm'};
+%     types = {'Control';'VEGFR Inhibition'};
+    
+    tnames = cell(length(regions),length(types));
+    rnames = cell(length(regions),length(types));
+    stats_means = zeros(length(regions),length(types));
+    stats_meds = zeros(length(regions),length(types));
+    for i=1:length(regions)
+        for j=1:length(types)
+            bReg = strcmpi(stats_tbl.region,regions{i});
+            bType = strcmpi(stats_tbl.type,types{j});
+            chk_stats = stats_tbl.(vname);
+            
+            tnames{i,j} = types{j};
+            rnames{i,j} = regions{i};
+            stats_means(i,j) = mean(chk_stats(bReg&bType));
+            stats_meds(i,j) = median(chk_stats(bReg&bType));
+        end
+    end
+    
+    stbl = table(tnames(:),rnames(:),stats_means(:),stats_meds(:), 'VariableNames',{'type','region','mean','median'});
+    
+    typeStr = strjoin(types, ',');
+    fprintf(fid, 'Average %s,%s\n', title, typeStr);
+    for i=1:length(regions)
+        nstr = arrayfun(@(x)(num2str(x,'%f')), stats_means(i,:), 'UniformOutput',false);
+        fprintf(fid, '%s,%s\n', regions{i}, strjoin(nstr,','));
+    end
+    fprintf(fid, '\n');
+    
+    fprintf(fid, 'Median %s,%s\n', title, typeStr);
+    for i=1:length(regions)
+        nstr = arrayfun(@(x)(num2str(x,'%f')), stats_meds(i,:), 'UniformOutput',false);
+        fprintf(fid, '%s,%s\n', regions{i}, strjoin(nstr,','));
+    end
+    fprintf(fid, '\n');
+    
+end
diff --git a/+Stats/write_summary_spicule.m b/+Stats/write_summary_spicule.m
new file mode 100644
index 0000000000000000000000000000000000000000..8607da8f9847038605d1f970dee91898dea3a149
--- /dev/null
+++ b/+Stats/write_summary_spicule.m
@@ -0,0 +1,27 @@
+function stbl = write_summary_spicule(fid, spc_tbl, fieldname, cmp_title)
+    distances = unique(spc_tbl.sp_dist);
+    
+    stats_means = zeros(length(distances),1);
+    stats_meds = zeros(length(distances),1);
+    for i=1:length(distances)
+        bDist = (spc_tbl.sp_dist == distances(i));
+        
+        stats_means(i) = mean(spc_tbl.(fieldname)(bDist));
+        stats_meds(i) = median(spc_tbl.(fieldname)(bDist));
+    end
+    
+    stbl = table(distances(:),stats_means(:),stats_meds(:), 'VariableNames',{'sp_distance','mean','median'});
+    
+    groupStr = 'Average Spicule Distance';
+    fprintf(fid, '%s,Average %s\n', groupStr, cmp_title);
+    for i=1:length(distances)
+        fprintf(fid, '%d,%f\n', distances(i), stats_means(i));
+    end
+    fprintf(fid, '\n');
+    
+    fprintf(fid, '%s,Median %s\n', groupStr, cmp_title);
+    for i=1:length(distances)
+        fprintf(fid, '%d,%f\n', distances(i), stats_meds(i));
+    end
+    fprintf(fid, '\n');
+end
diff --git a/plot_axt.py b/plot_axt.py
new file mode 100644
index 0000000000000000000000000000000000000000..51d63cf450c16418dee5dbcf031ecdd30b569406
--- /dev/null
+++ b/plot_axt.py
@@ -0,0 +1,166 @@
+import pandas as pd
+import matplotlib
+import matplotlib.pyplot as plt
+import seaborn as sns
+# import statannot as sta
+
+data = pd.read_csv('ecto_size_stats_axtinib.csv')
+
+##
+plt.figure(figsize=(10,5))
+# matplotlib.rcParams['figure.dpi'] = 200
+ax = sns.boxplot(x="region", y="size", hue="type", showmeans=True, data=data, palette='Set1',
+    meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+ax.set_ylim(0,1.2)
+ax.set_ylabel('')
+ax.set_xlabel('')
+# ax.set_title('Vesicle Average Volume [um^3]', fontsize=20)
+ax.set_title('')
+ax.legend().set_title('')
+ax.legend(loc='upper left')
+ax.tick_params(labelsize=16)
+# ax.set(xticklabels=['Control','VEGFR Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+plt.setp(ax.get_legend().get_texts(), fontsize=16)
+plt.tight_layout()
+plt.savefig('C:/Users/mwinter/Desktop/figs/size_axt.svg')
+
+
+#####################
+data = pd.read_csv('ecto_track_stats_axtinib.csv')
+
+##
+plt.figure(figsize=(5,4.5))
+# matplotlib.rcParams['figure.dpi'] = 200
+ax = sns.boxplot(x="region", y="avg_speed", hue="type", showmeans=True, data=data, palette='Set1',
+    meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+ax.set_ylim(0.001,0.2)
+ax.set_ylabel('')
+ax.set_xlabel('')
+ax.yaxis.set_major_locator(plt.MaxNLocator(4))
+# ax.set_title('Vesicle Average Speed [um/s]', fontsize=20)
+ax.set_title('')
+ax.legend().set_title('')
+ax.legend(loc='upper left')
+ax.tick_params(labelsize=16)
+# ax.set(xticklabels=['Control','VEGFR Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+plt.setp(ax.get_legend().get_texts(), fontsize=16)
+plt.tight_layout()
+plt.savefig('C:/Users/mwinter/Desktop/figs/track_speed.svg')
+
+# ##
+# plt.figure()
+# ax = sns.boxplot(x="type", y="path_ratio", hue="region", data=data, palette='Set3')
+# # ax.set_ylim(0,1.0)
+# ax.set_ylabel('')
+# ax.set_xlabel('')
+# ax.set_title('Vesicle Path Ratio', fontsize=20)
+# ax.legend().set_title('')
+# ax.tick_params(labelsize=16)
+# # ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# # ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+# plt.setp(ax.get_legend().get_texts(), fontsize=18)
+
+# ##
+# plt.figure()
+# ax = sns.boxplot(x="type", y="win_path_ratio", hue="region", data=data, palette='Set1')
+# # ax.set_ylim(0,1.0)
+# ax.set_ylabel('')
+# ax.set_xlabel('')
+# ax.set_title('Vesicle Windowed Path Ratio', fontsize=20)
+# ax.legend().set_title('')
+# ax.tick_params(labelsize=16)
+# # ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# # ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+# plt.setp(ax.get_legend().get_texts(), fontsize=18)
+
+##
+plt.figure(figsize=(5,4.5))
+ax = sns.boxplot(x="region", y="dir_index", hue="type", showmeans=True, data=data, palette='Set1',
+    meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+# ax.set_ylim(0,0.2)
+ax.set_ylabel('')
+ax.set_xlabel('')
+# ax.set_title('Vesicle Directionality Index', fontsize=20)
+ax.set_title('')
+ax.legend().set_title('')
+ax.legend(loc='upper left')
+ax.tick_params(labelsize=16)
+# ax.set(xticklabels=['Control','VEGFR Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+plt.setp(ax.get_legend().get_texts(), fontsize=16)
+plt.tight_layout()
+plt.savefig('C:/Users/mwinter/Desktop/figs/track_di.svg')
+
+##
+plt.figure(figsize=(5,4.5))
+# matplotlib.rcParams['figure.dpi'] = 200
+ax = sns.boxplot(x="region", y="nimsd_D", hue="type", showmeans=True, data=data, palette='Set1',
+    meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+ax.set_ylim(0.0,0.05)
+ax.set_ylabel('')
+ax.set_xlabel('')
+ax.yaxis.set_major_locator(plt.MaxNLocator(4))
+# ax.set_title('Vesicle Average Speed [um/s]', fontsize=20)
+# ax.set_title('Non-Ideal Diffusion Coefficient [um^2/s]')
+ax.legend().set_title('')
+ax.legend(loc='upper left')
+ax.tick_params(labelsize=16)
+# ax.set(xticklabels=['Control','VEGFR Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+plt.setp(ax.get_legend().get_texts(), fontsize=16)
+plt.tight_layout()
+plt.savefig('C:/Users/mwinter/Desktop/figs/track_nidiff_D.svg')
+
+##
+plt.figure(figsize=(5,4.5))
+# matplotlib.rcParams['figure.dpi'] = 200
+ax = sns.boxplot(x="region", y="nimsd_alpha", hue="type", showmeans=True, data=data, palette='Set1',
+    meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+ax.set_ylim(0.5,2.5)
+ax.set_ylabel('')
+ax.set_xlabel('')
+ax.yaxis.set_major_locator(plt.MaxNLocator(4))
+ax.legend().set_title('')
+ax.legend(loc='upper left')
+ax.tick_params(labelsize=16)
+# ax.set(xticklabels=['Control','VEGFR Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+plt.setp(ax.get_legend().get_texts(), fontsize=16)
+plt.tight_layout()
+plt.savefig('C:/Users/mwinter/Desktop/figs/track_nidiff_alpha.svg')
+
+##
+plt.figure(figsize=(5,4.5))
+# matplotlib.rcParams['figure.dpi'] = 200
+ax = sns.boxplot(x="region", y="msd_D", hue="type", showmeans=True, data=data, palette='Set1',
+    meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+ax.set_ylim(0,0.03)
+ax.set_ylabel('')
+ax.set_xlabel('')
+ax.yaxis.set_major_locator(plt.MaxNLocator(4))
+ax.legend().set_title('')
+ax.legend(loc='upper left')
+ax.tick_params(labelsize=16)
+# ax.set(xticklabels=['Control','VEGFR Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+plt.setp(ax.get_legend().get_texts(), fontsize=16)
+plt.tight_layout()
+plt.savefig('C:/Users/mwinter/Desktop/figs/track_diff_D.svg')
+
+##
+plt.figure(figsize=(5,4.5))
+# matplotlib.rcParams['figure.dpi'] = 200
+msdr2 = [i**2 for i in data['msd_R']]
+ax = sns.distplot(msdr2, kde=False, norm_hist=False, color=[0.07,0.30,0.59], hist_kws={'alpha':1})
+ax.set_ylabel('')
+ax.set_xlabel('')
+plt.tight_layout()
+plt.savefig('C:/Users/mwinter/Desktop/figs/track_diff_R_hist.svg')
diff --git a/plot_bb94.py b/plot_bb94.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9c7c466418692f612944e319cc6f2c569334dbb
--- /dev/null
+++ b/plot_bb94.py
@@ -0,0 +1,25 @@
+import pandas as pd
+import matplotlib
+import matplotlib.pyplot as plt
+import seaborn as sns
+
+data = pd.read_csv('ecto_size_stats_bb94.csv')
+
+##
+plt.figure(figsize=(10,5))
+# matplotlib.rcParams['figure.dpi'] = 200
+ax = sns.boxplot(x="region", y="size", hue="type", data=data, palette='Set1',
+    showmeans = True, meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+ax.set_ylim(0,1.0)
+ax.set_ylabel('')
+ax.set_xlabel('')
+# ax.set_title('Vesicle Average Volume [um^3]', fontsize=20)
+ax.set_title('')
+ax.legend().set_title('')
+ax.legend(loc='upper left')
+ax.tick_params(labelsize=14)
+# ax.set(xticklabels=['Control','Metalloproteinase Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+plt.setp(ax.get_legend().get_texts(), fontsize=14)
+plt.savefig('C:/Users/mwinter/Desktop/figs/mmpl/size_bb94.svg')
diff --git a/plot_spicule_stats.py b/plot_spicule_stats.py
new file mode 100644
index 0000000000000000000000000000000000000000..78ef25726e8bba1bba392ed3590773b88673959b
--- /dev/null
+++ b/plot_spicule_stats.py
@@ -0,0 +1,49 @@
+import pandas as pd
+import matplotlib
+import matplotlib.pyplot as plt
+import seaborn as sns
+
+#####################
+data = pd.read_csv('spicule_stats.csv')
+
+##
+plt.figure(figsize=(4.8,5))
+ax = sns.boxplot(x="sp_dist", y="abs_vel", data=data, color='cornflowerblue', showmeans=True,meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+# ax.set_ylim(0,0.2)
+ax.set_ylabel('')
+ax.set_xlabel('')
+# ax.set_title('Vesicle Directionality Index', fontsize=20)
+ax.set_title('')
+# ax.legend().set_title('')
+# ax.legend(loc='upper left')
+ax.tick_params(labelsize=16)
+ax.set_xticklabels(['1','2','3','4','5','6','7','8','9','10'])
+# ax.set(xticklabels=['Control','VEGFR Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+# plt.setp(ax.get_legend().get_texts(), fontsize=16)
+plt.tight_layout()
+plt.setp(ax.artists, edgecolor = 'k')
+plt.setp(ax.lines, color='k')
+plt.savefig('C:/Users/mwinter/Desktop/figs/sp_dist_v_vel.svg')
+
+##
+plt.figure(figsize=(4.8,5))
+ax = sns.boxplot(x="sp_dist", y="inst_vel", data=data, color='cornflowerblue', showmeans=True,meanprops={"marker":"s","markerfacecolor":"white", "markeredgecolor":"black"})
+ax.set_ylim(-0.4,0.4)
+ax.set_ylabel('')
+ax.set_xlabel('')
+# ax.set_title('Vesicle Directionality Index', fontsize=20)
+ax.set_title('')
+# ax.legend().set_title('')
+# ax.legend(loc='upper left')
+ax.tick_params(labelsize=16)
+ax.set_xticklabels(['1','2','3','4','5','6','7','8','9','10'])
+# ax.set(xticklabels=['Control','VEGFR Inhibition'])
+# ax.yaxis.set_major_locator(matplotlib.ticker.MultipleLocator(5))
+# ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
+# plt.setp(ax.get_legend().get_texts(), fontsize=16)
+plt.tight_layout()
+plt.setp(ax.artists, edgecolor = 'k')
+plt.setp(ax.lines, color='k')
+plt.savefig('C:/Users/mwinter/Desktop/figs/sp_dist_v_prjdist.svg')