Skip to content

Commit

Permalink
More flexible splitting options
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Liddell committed Mar 29, 2019
1 parent 923c12c commit 7df290a
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 52 deletions.
108 changes: 94 additions & 14 deletions +jrclust/+curate/@CurateController/autoSplit.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,69 @@ function autoSplit(obj, multisite)

clusterTimes = obj.hClust.spikeTimes(iSpikes);

nSplits = jrclust.utils.inputdlgNum('Number of clusters:', '', 2);
if isnan(nSplits)
return;
elseif nSplits < 2 || nSplits ~= round(nSplits)
jrclust.utils.qMsgBox('Please enter an integer >= 2');
% create split figure
hFigSplit = jrclust.views.Figure('FigSplit', [0.05 0.05 0.8 0.8], sprintf('Split cluster %d', iCluster), 0, 0);
hFigSplit.figApply(@set, 'Visible', 'off');
hFigSplit.figData.nSplits = 0;

% create a dialog
splitDlg = dialog('Name', 'Split cluster', ...
'Units', 'Normalized', ...
'Position', [0.4, 0.5, 0.2, 0.15]);

uicontrol('Parent', splitDlg, 'Style', 'text', ...
'String', 'Number of splits', ...
'Units', 'Normalized', ...
'Position', [0.4 0.4 0.2 0.07]);

nsplit = uicontrol('Parent', splitDlg, 'Style', 'edit', ...
'String', 2, ...
'Units', 'Normalized', ...
'Position', [0.4 0.25 0.2 0.15]);

% button group: K-means, K-medoids, Hierarchical clustering
btngrp = uibuttongroup('Parent', splitDlg, 'Units', 'Normalized', ...
'Position', [0.1 0.1 0.3 0.8]);
uicontrol('Parent', btngrp, 'Style', 'radiobutton', 'String', 'Hierarchical (Ward)', ...
'Units', 'Normalized', 'Position', [0.1, 0.1, 1, 0.15]);
uicontrol('Parent', btngrp, 'Style', 'radiobutton', 'String', 'K-means', ...
'Units', 'Normalized', 'Position', [0.1, 0.7, 1, 0.15]);
uicontrol('Parent', btngrp, 'Style', 'radiobutton', 'String', 'K-medoids', ...
'Units', 'Normalized', 'Position', [0.1, 0.4, 1, 0.15]);

uicontrol('Parent', splitDlg, 'Style', 'pushbutton', ...
'String', 'Split', ...
'Units', 'Normalized', ...
'Position', [0.4 0.1 0.2 0.15], ...
'Callback', @(hO, hE) preSplit(splitDlg, nsplit.String, btngrp, hFigSplit));
uicontrol('Parent', splitDlg, 'Style', 'pushbutton', ...
'String', 'Cancel', ...
'Units', 'Normalized', ...
'Position', [0.6 0.1 0.2 0.15], ...
'Callback', @(hO, hE) doCancel(splitDlg, hFigSplit));
uicontrol('Parent', splitDlg, 'Style', 'text', ...
'String', 'How to recluster this unit?', ...
'Units', 'Normalized', ...
'Position', [0.1 0.9 0.3 0.07]);

uiwait(splitDlg);

% nSplits = jrclust.utils.inputdlgNum('Number of clusters:', '', 2);
% if isnan(nSplits)
% return;
% elseif nSplits < 2 || nSplits ~= round(nSplits)
% jrclust.utils.qMsgBox('Please enter an integer >= 2');
% return;
% end

if hFigSplit.figData.nSplits <= 1
return;
else
nSplits = hFigSplit.figData.nSplits;
end

hBox = jrclust.utils.qMsgBox('Splitting... (this closes automatically)', 0, 1);

% create split figure
hFigSplit = jrclust.views.Figure('FigSplit', [0.05 0.05 0.8 0.8], 'Split', 0, 0);

% add plots
hFigSplit.addAxes('vppTime', 'Units', 'Normalized', 'OuterPosition', [0 0 0.7 0.5]);
hFigSplit.addAxes('pc12', 'Units', 'Normalized', 'OuterPosition', [0 0.5 0.2333 0.5]);
Expand Down Expand Up @@ -106,7 +156,7 @@ function autoSplit(obj, multisite)
hFigSplit.figData.iSite = iSite;
hFigSplit.figData.refracInt = obj.hCfg.refracInt/1000;
while 1
[assigns, pcaFeatures] = doAutoSplit(sampledTraces, [double(clusterTimes) double(localVpp')], nSplits); %TW
[assigns, pcaFeatures] = doAutoSplit(sampledTraces, [double(clusterTimes) double(localVpp')], hFigSplit); %TW
hFigSplit.figData.pcaFeatures = pcaFeatures;
hFigSplit.figData.clusterTimes = double(clusterTimes)/obj.hCfg.sampleRate;
hFigSplit.figData.localVpp = double(localVpp');
Expand Down Expand Up @@ -135,7 +185,7 @@ function autoSplit(obj, multisite)
end

%% LOCAL FUNCTIONS
function [assigns, pcaFeatures] = doAutoSplit(sampledSpikes, spikeFeatures, nSplits)
function [assigns, pcaFeatures] = doAutoSplit(sampledSpikes, spikeFeatures, hFigSplit)
%DOAUTOSPLIT
% TODO: ask users number of clusters and split multi-way
%Make automatic split of clusters using PCA + hierarchical clustering
Expand All @@ -146,14 +196,44 @@ function autoSplit(obj, multisite)
% ask how many clusters there are
try
combinedFeatures = (combinedFeatures - mean(combinedFeatures, 1)) ./ std(combinedFeatures, 1);
assigns = clusterdata(combinedFeatures, 'linkage', 'ward', ...
'distance', 'euclidean', ...
'maxclust', nSplits, 'savememory', 'on');
catch % not enough features to automatically split
assigns = hFigSplit.figData.hFunSplit(combinedFeatures);
catch % not enough features to automatically split or some other failure
assigns = ones(nSpikes, 1);
end
end

function doCancel(splitDlg, hFigSplit)
delete(splitDlg);
hFigSplit.figData.nSplits = 0;
end

function preSplit(splitDlg, nsplit, btngrp, hFigSplit)
whichSelected = logical(arrayfun(@(child) child.Value, btngrp.Children));
selected = btngrp.Children(whichSelected);

nsplit = floor(str2double(nsplit));
if isnan(nsplit)
nsplit = 2;
end

switch selected.String
case 'K-means'
hFigSplit.figData.hFunSplit = @(X) kmeans(X, nsplit);
case 'K-medoids'
hFigSplit.figData.hFunSplit = @(X) kmedoids(X, nsplit);
otherwise
hFigSplit.figData.hFunSplit = @(X) clusterdata(X, ...
'maxclust', nsplit, ...
'linkage', 'ward', ...
'distance', 'euclidean', ...
'savememory', 'on');
end

delete(splitDlg)

hFigSplit.figData.nSplits = nsplit;
end

function updateSplitPlots(hFigSplit)
clustList = hFigSplit.figData.clustList;

Expand Down
58 changes: 30 additions & 28 deletions +jrclust/+curate/@CurateController/keyPressFigProj.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,34 +62,36 @@ function keyPressFigProj(obj, ~, hEvent)
obj.updateFigProj(1);

case 's' %split
disp('Split: not implemented yet');
% if numel(obj.selected) == 1
% iCluster = obj.selected(1);
%
% hFigProj.addPlot('hPoly', @impoly)
% polyPos = hFigProj.plotApply('hPoly', @getPosition);
%
% XData = hFigProj.plotApply('foreground', @get, 'XData');
% YData = hFigProj.plotApply('foreground', @get, 'YData');
%
% retained = inpolygon(XData, YData, polyPos(:,1), polyPos(:,2));
% jSites = unique(floor(XData(retained))) + 1;
% iSites = unique(floor(YData(retained))) + 1;
%
% % return here
% hFigProj.addPlot('hSplit', @line, XData(retained), YData(retained), ...
% 'Color', obj.hCfg.colorMap(3, :), ...
% 'Marker', '.', 'LineStyle', 'none');
%
% dlgAns = questdlg('Split?', 'Confirmation', 'No');
%
% hFigProj.rmPlot('hPoly');
% hFigProj.rmPlot('hSplit');
%
% if strcmp(dlgAns, 'Yes')
% obj.splitCluster(iCluster, retained);
% end
% end
if numel(obj.selected) == 1
iCluster = obj.selected(1);

hFigProj.addPlot('hPoly', @impoly)
polyPos = hFigProj.plotApply('hPoly', @getPosition);
if isempty(polyPos)
return;
end

XData = hFigProj.plotApply('foreground', @get, 'XData');
YData = hFigProj.plotApply('foreground', @get, 'YData');

retained = inpolygon(XData, YData, polyPos(:,1), polyPos(:,2));

% return here
hFigProj.addPlot('hSplit', @line, XData(retained), YData(retained), ...
'Color', obj.hCfg.colorMap(3, :), ...
'Marker', '.', 'LineStyle', 'none');

dlgAns = questdlg('Split?', 'Confirmation', 'No');

hFigProj.rmPlot('hPoly');
hFigProj.rmPlot('hSplit');

if strcmp(dlgAns, 'Yes')
iSpikes = obj.hClust.spikesByCluster{iCluster};
retained = ismember(hFigProj.figData.dispFeatures.fgSpikes(retained), iSpikes);
obj.splitCluster(iCluster, retained);
end
end

end % switch
end
8 changes: 5 additions & 3 deletions +jrclust/+views/plotFigProj.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
plotFeatures(hFigProj, 'background', bgYData, bgXData, boundScale, hCfg);

% plot foreground spikes
plotFeatures(hFigProj, 'foreground', fgYData, fgXData, boundScale, hCfg);
assigns = plotFeatures(hFigProj, 'foreground', fgYData, fgXData, boundScale, hCfg);
hFigProj.figData.dispFeatures.fgSpikes = hFigProj.figData.dispFeatures.fgSpikes(assigns);

% plot secondary foreground spikes
if numel(selected) == 2
plotFeatures(hFigProj, 'foreground2', fg2YData, fg2XData, boundScale, hCfg);
Expand Down Expand Up @@ -82,14 +84,14 @@
end

%% LOCAL FUNCTIONS
function plotFeatures(hFigProj, plotKey, featY, featX, boundScale, hCfg)
function assigns = plotFeatures(hFigProj, plotKey, featY, featX, boundScale, hCfg)
%PLOTFEATURES Plot features in a grid
if strcmp(hCfg.dispFeature, 'vpp')
bounds = boundScale*[0 1];
else
bounds = boundScale*[-1 1];
end

[XData, YData] = ampToProj(featY, featX, bounds, hCfg.nSiteDir, hCfg);
[XData, YData, assigns] = ampToProj(featY, featX, bounds, hCfg.nSiteDir, hCfg);
hFigProj.updatePlot(plotKey, XData, YData);
end
8 changes: 6 additions & 2 deletions +jrclust/+views/private/ampToProj.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function [XData, YData, validXY, boxShape] = ampToProj(YData, XData, bounds, maxPair, hCfg)
function [XData, YData, assigns] = ampToProj(YData, XData, bounds, maxPair, hCfg)
%AMPTOPROJ Reshape feature data from nSpikes x nSites to display in an
%nSites x nSites grid
% input: YData, nSpikes x nSites, y-values for feature projection
Expand All @@ -13,12 +13,15 @@

% spike features translated into site-site boxes
[boxedX, boxedY] = deal(nan([nSpikes, nSites, nSites], 'single'));
assigns = zeros([nSpikes, nSites, nSites]);

for jSite = 1:nSites
jSiteY = YData(:, jSite);
yMask = jSiteY > 0 & jSiteY < 1; % get points away from the boundaries

for iSite = 1:nSites
assigns(:, jSite, iSite) = 1:nSpikes;

if abs(iSite - jSite) > maxPair
continue;
end
Expand Down Expand Up @@ -46,7 +49,8 @@
YData = boxedY(validXY);
YData = YData(:);

boxShape = [nSpikes, nSites, nSites];
assigns = assigns(validXY);
assigns = assigns(:);
end

%% LOCAL FUNCTIONS
Expand Down
2 changes: 1 addition & 1 deletion +jrclust/+views/private/getFigProjFeatures.m
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
'bgXData', bgXData, ...
'fgYData', fgYData, ...
'fgXData', fgXData, ...
'fgSpikes', fgSpikes, ...
'fgSpikes', fgSpikes, ... % for finding spikes to split off
'fg2YData', fg2YData, ...
'fg2XData', fg2XData, ...
'fg2Spikes', fg2Spikes);
Expand Down
13 changes: 10 additions & 3 deletions CHANGELOG.TXT
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
29 Mar 2019 (4.0.0-beta release)

- Allow user to select exploratory clustering algorithm in main split.
- Allow user to split in FigProj.


27 Mar 2019

Export results in phy-compatible format with jrc export-phy /path/to/your/config.prm
Display summaries after detect and sort, also added an option to display a summary in UI.
- Bugfix: non-integral sample rates are supported to full precision.
- Export results in phy-compatible format with jrc export-phy /path/to/your/config.prm.
- Display summaries after detect and sort, also added an option to display a summary in UI.

20 Mar 2019 (4.0.0-beta release)
20 Mar 2019

- Bugfix: import-ksort didn't use the correct sites for imported spikes; this has been fixed.

Expand Down
2 changes: 1 addition & 1 deletion json/jrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"tag": "beta",
"codename": "Edward"
},
"changeDate": "27 March 2019",
"changeDate": "29 March 2019",
"authors": [
"James Jun"
],
Expand Down

0 comments on commit 7df290a

Please sign in to comment.