Skip to content

Commit 2176040

Browse files
authored
Bugfix/sortfix (#84)
* Sort bugfix Overall minor fixes to get Sort interface to work properly - Recluster kmeans now is done on the feature space obtained by the wavelet decomposition. I'm planning to include some toggles to toggle between feature spaces. SpikeImage Added some other shortcuts. Will add documentation in the wiki soon. * Diskdata tweaks Proposed diskdata tweaks. Should improve performance a little. It is taking a really long time to save and load all the required data during sorting. Major tweak: hd5write/read calls reduced by writing/loading files in chunks instead of column by column. Old version is commented out instead of removing it to make it easily reversible. * UImenuclick_sort fix Modified uimenuclick to let the sortObj figure out what to do with the input arg. Also added a small error if the user tries to sort more then one animal or the tank. * Dashboard opening fix Ported changes from multiAnimals branch to fix an issue with the dashboard not opening correctly * fix nargout in diskdata fixed weird bug that was causing subsref to crash due to a wrong number returned by nargout. This is not a definitive fix. Also, is happening only on my laptop (Matlab 2017a and b) but not on my workstation (2017a and 2019b). Will investigate further. * Revert commit for demo * various fixes Various fixes. the demo_nigelab now works to the end. A small tweak in nigelObj to make sure that the data are correctly retrieved when moved. Fixed a very rare fix in the artifact detection step. getNigelPath was returning a hardcoded path to kumc internal directories when run in unix.
1 parent cb16d8d commit 2176040

14 files changed

Lines changed: 148 additions & 128 deletions

File tree

+nigeLab/+libs/@DashBoard/DashBoard.m

Lines changed: 35 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -653,16 +653,12 @@ function buildButtons(obj,nigelPanelObj)
653653
{@obj.toggleSplitMultiAnimalsUI,'start'})];
654654

655655
% By default, buttons are enabled
656-
% if obj.SelectionIndex(1,2) == 0
656+
if ~any([obj.Tank.Children.MultiAnimals])
657657
setButton(obj.nigelButtons.Tree,'Split','Enable','off');
658-
% end
659-
660-
% obj.Listener = [obj.Listener, ...
661-
% addlistener(obj,'SelectionIndex','PostSet',...
662-
% @(~,~)obj.toggleSplitUIMenuEnable)];
663-
658+
end
664659
end
665660

661+
666662
% Returns figure handle, with layout mediated by core nigelPanels
667663
function fig = buildGUI(obj)
668664
% LOADPANELS Method to create all custom uipanels (nigelPanels)
@@ -1414,9 +1410,18 @@ function uiCMenuClick_Sort(obj,~,~)
14141410
% mitem.Callback = @(~,~)obj.uiCMenuClick_Sort;
14151411
%
14161412
% Runs the spike sorting interface.
1417-
1418-
blockObj = obj.getSelectedItems('obj');
1419-
Sort(blockObj); % Invoke sorting interface
1413+
nigelObj = obj.Tree.SelectedItems;
1414+
switch nigelObj.Type
1415+
case 'Block'
1416+
Sort(nigelObj);
1417+
case 'Animal'
1418+
if numel(nigelObj)>1
1419+
error('You can only sort one aniaml at a time!');
1420+
end
1421+
Sort(nigelObj);
1422+
case 'Tank'
1423+
error('Sort operation is not possible on the whole tank at once.\nPlease select an Animal or a Block.');
1424+
end
14201425
end
14211426

14221427
% LISTENER CALLBACK: Menu item toggle child mask on "Enable" click
@@ -1499,7 +1504,8 @@ function updateAfterSelectionChanged(obj,src,evt)
14991504
end
15001505

15011506

1502-
end
1507+
end
1508+
15031509

15041510
% RESTRICTED: nigeLab.libs.splitMultiAnimalsUI
15051511
methods (Access=?nigeLab.libs.splitMultiAnimalsUI)
@@ -1517,101 +1523,38 @@ function toggleSplitMultiAnimalsUI(obj,mode)
15171523

15181524
switch mode
15191525
case 'start'
1520-
bl = obj.getSelectedItems;
1521-
% if more than one block is selected, select the first
1522-
% one with a multiAnimal
1523-
indx = find([bl.MultiAnimals],1);
1524-
if isempty(indx)
1525-
errordlg('This is not a multiAnimal!');
1526-
return;
1527-
end % if ~MultiAnimals
1528-
bl = bl(indx);
1529-
Index = obj.selectedItems2Index(bl,obj.Tank);
1530-
obj.Tree.SelectedNodes = obj.Tree.Root.Children(Index(2)).Children(Index(3));
1531-
1532-
1533-
% Ensure that only 1 "child" object is selected at a time
1534-
obj.getChild('TreePanel').getChild('Tree').SelectionType = 'single';
1535-
if isvalid(obj.splitMultiAnimalsUI)
1536-
obj.splitMultiAnimalsUI.toggleVisibility;
1537-
return;
1538-
else
1539-
% 'start' is only entered via button-click
1540-
toggleSplitMultiAnimalsUI(obj,'init');
1541-
drawnow;
1542-
toggleSplitMultiAnimalsUI(obj,'start');
1543-
return;
1544-
end % if isvalid
1545-
1546-
% TODO disable nodes without multiAnimal flag!
1547-
% [obj.Tree.Root.Children(find([obj.Tank.Children.MultiAnimals])).Enable] = deal('off');
1526+
% pop multi aniamsl interface up
1527+
if ~isempty(obj.splitMultiAnimalsUI) && isvalid(obj.splitMultiAnimalsUI)
1528+
obj.splitMultiAnimalsUI.toggleVisibility('on');
1529+
obj.Tree.changeTreeSelection([])
1530+
else
1531+
toggleSplitMultiAnimalsUI(obj,'init');
1532+
toggleSplitMultiAnimalsUI(obj,'start');
1533+
end
1534+
15481535
case 'stop'
1549-
obj.getChild('TreePanel').getChild('Tree').SelectionType = ...
1550-
'discontiguous';
1536+
15511537
% TODO reenable nodes without multiAnimal flag!
15521538
if any([obj.Tank.Children.MultiAnimals])
15531539
obj.splitMultiAnimalsUI.toggleVisibility;
15541540
else
15551541
delete( obj.splitMultiAnimalsUI.Fig);
15561542
delete(obj.splitMultiAnimalsUI);
1557-
listenerIndex = strcmp({obj.Listener.eventName},'SplitCompleted');
1558-
delete(obj.Listener(listenerIndex));
1559-
obj.Listener(listenerIndex) = [];
15601543
end
15611544

15621545
case 'init'
1563-
% First, make sure the selection is valid
1564-
1565-
% The multiAnimalsUI must be opened
1566-
SelectedItems = cat(1,obj.Tree.SelectedNodes.UserData);
1567-
% Note that SelectedItems only contains nodes of a specific
1568-
% type, based on exclusion done in `treeSelectionFcn`.
1569-
% Therefore the vertical concatenation above is always valid
1570-
nCol = size(SelectedItems,2);
1571-
switch nCol
1572-
case 0 % tank
1573-
% Cannot be invoked at "TANK" level
1574-
error(['nigeLab:' mfilename ':badCase'],...
1575-
'Should not be able to enter split UI from TANK level.');
1576-
case 1 % animal
1577-
% Gets all blocks of selected animals
1578-
B = obj.Tank{SelectedItems(1),:};
1579-
A = repmat(obj.Tank{SelectedItems(1)},1,numel(B));
1580-
for i = 1:numel(SelectedItems)
1581-
b = obj.Tank{SelectedItems(i),:};
1582-
B = [B, b];
1583-
A = [A, ...
1584-
repmat(obj.Tank{SelectedItems(i)},1,numel(b))];
1585-
end
1586-
1587-
case 2 % block
1588-
% Get specific subset of block or blocks
1589-
A = obj.Tank{SelectedItems(:,1)};
1590-
B = obj.Tank{SelectedItems};
1591-
end % switch nCol
1546+
% First, make sure the Tank contains multiAnimals
15921547

1593-
if ~all([A.MultiAnimals])
1594-
return;
1595-
end
1596-
1597-
if ~all([B.MultiAnimals])
1598-
return;
1599-
end
1548+
an = obj.Tank.Children;
1549+
an = an([an.MultiAnimals]);
16001550

1601-
obj.toSplit = struct('Animal',cell(numel(A),1),...
1602-
'Block',cell(numel(B),1));
1603-
for i = 1:numel(obj.toSplit)
1604-
obj.toSplit(i).Animal = A(i);
1605-
obj.toSplit(i).Block = B(i);
1606-
end
1607-
obj.toAdd = struct('Animal',{},'Block',{});
1551+
if isempty(an)
1552+
errordlg('This is not a multiAnimal!');
1553+
return;
1554+
end % if ~MultiAnimals
16081555

1609-
obj.splitMultiAnimalsUI = ...
1610-
nigeLab.libs.splitMultiAnimalsUI(obj);
1556+
obj.splitMultiAnimalsUI = nigeLab.libs.splitMultiAnimalsUI(an);
16111557

1612-
obj.Listener = [obj.Listener,...
1613-
addlistener(obj.splitMultiAnimalsUI,'SplitCompleted',...
1614-
@(~,e)obj.addToTree(e.nigelObj))];
16151558
end % switch mode
16161559
end
16171560

+nigeLab/+libs/@DiskData/getEventsFromIndexing.m

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,30 @@
3636
iCol = 1:obj.size_(2);
3737
end
3838

39-
% First step: return full dataset using iCol
40-
varname_ = ['/' obj.name_];
41-
data = nan(obj.size_(1),numel(iCol));
42-
for i = 1:numel(iCol)
43-
data(:,i)=h5read(obj.diskfile_,varname_,[1 iCol(i)],[N 1]);
44-
end
39+
%% optimized FB 2020/04/01
40+
% % First step: return full dataset using iCol
41+
% varname_ = ['/' obj.name_];
42+
% data = nan(obj.size_(1),numel(iCol));
43+
% for i = 1:numel(iCol)
44+
% data(:,i)=h5read(obj.diskfile_,varname_,[1 iCol(i)],[N 1]);
45+
% end
46+
47+
48+
% step 0. To speed up and avoid calling h5read too many times lets find
49+
% adjacent chunks of iCol
50+
iCol = iCol(:);
51+
clustIdx = find([1;diff(iCol)-1]); % this is the starting index of clusters
52+
stride = diff(clustIdx(:));
53+
ColBlocks = [iCol(clustIdx(:)) [stride;numel(iCol)-sum(stride)]];
54+
55+
% First step: return full dataset using iCol
56+
varname_ = ['/' obj.name_];
57+
data = nan(obj.size_(1),sum(ColBlocks(:,2)));
58+
st = 0;
59+
for i = 1:size(ColBlocks,1)
60+
data(:,st+1:st+ColBlocks(i,2))=h5read(obj.diskfile_,varname_,[1 ColBlocks(i,1)],[N ColBlocks(i,2)]);
61+
st = st + ColBlocks(i,2);
62+
end
4563

4664
% Second step: return reduced subset using iRow
4765
data = data(iRow,:);

+nigeLab/+libs/@DiskData/setEventsFromIndexing.m

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,43 @@ function setEventsFromIndexing(obj,iRow,iCol,data)
3939
data = repmat(data,numel(iRow),numel(iCol));
4040
end
4141

42+
% step 0. To speed up and avoid calling h5read too many times lets find
43+
% adjacent chunks of iCol
44+
iCol = iCol(:);
45+
clustIdx = find([1;diff(iCol)-1]); % this is the starting index of clusters
46+
strideCol = diff(clustIdx(:));
47+
ColBlocks = [iCol(clustIdx(:)) [strideCol;numel(iCol)-sum(strideCol)]];
48+
49+
% same for iRow
50+
iRow = iRow(:);
51+
clustIdx = find([1;diff(iRow)-1]); % this is the starting index of clusters
52+
strideRow = diff(clustIdx(:));
53+
RowBlocks = [iRow(clustIdx(:)) [strideRow;numel(iRow)-sum(strideRow)]];
54+
4255
% Do the H5 assignment
4356
varname_ = ['/' obj.name_];
4457

4558
% Iterate on columns, reading in the full column and then overwriting the
4659
% relevant rows. There should in general be many fewer Columns than rows,
4760
% unless the number of rows is so small that it becomes a trivial tradeoff
48-
for i = 1:numel(iCol)
49-
a = h5read(obj.diskfile_,varname_,[1 iCol(i)],[N 1]);
50-
assigned_data = data(:,i);
51-
a(iRow,1) = assigned_data;
52-
h5write(obj.diskfile_,varname_,a,[1 iCol(i)],[N 1]);
53-
end
61+
% for i = 1:numel(iCol)
62+
% a = h5read(obj.diskfile_,varname_,[1 iCol(i)],[N 1]);
63+
% assigned_data = data(:,i);
64+
% a(iRow,1) = assigned_data;
65+
% h5write(obj.diskfile_,varname_,a,[1 iCol(i)],[N 1]);
66+
% end
5467

68+
% write data to file
69+
stCol = 0;
70+
for ii = 1:size(ColBlocks,1)
71+
stRow = 0;
72+
for jj = 1:size(RowBlocks,1)
73+
h5write(obj.diskfile_,varname_,data(stRow+1:stRow+RowBlocks(jj,2),stCol+1:stCol+ColBlocks(ii,2)),...
74+
[RowBlocks(jj,1) ColBlocks(ii,1)],[RowBlocks(jj,2) ColBlocks(ii,2)]);
75+
stRow = stRow + RowBlocks(jj,2);
76+
end
77+
stCol = stCol + ColBlocks(ii,2);
78+
end
5579

5680

5781
end

+nigeLab/+libs/@DiskData/subsref.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
{'type','value','tag','ts','snippet','data'});
1818
mIdx = strcmp(diskMethods,S(1).subs);
1919
if any(mIdx) && ~any(strcmp(excludedPropNames,S(1).subs))
20-
if nargout > 0
20+
nOut = nargout;
21+
if nOut > 0
2122
data = builtin('subsref',obj,S);
2223
else
2324
if numel(m(mIdx).OutputNames) > 0

+nigeLab/+libs/@FeaturesUI/FeaturesUI.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ function Init(obj)
331331
obj.rotateImg = imread(fullfile(fileparts(mfilename('fullpath')),'private','rotate.png'));
332332

333333
obj.rotateButton = uicontrol(featureDisplayPanel,...
334-
'Style','pushbutton',...
334+
'Style','togglebutton',...
335335
'Units','Normalized',...
336336
'Position',[0.6 0 0.04 0.07],...
337337
'Callback',@obj.RotateBtnPress,...
@@ -575,9 +575,9 @@ function ChangeSize(obj,~,~)
575575
% Find subset of spikes to move based on convex-hull polygon
576576
function GetSpikesToMove(obj,ax)
577577
%GETSPIKESTOMOVE Draw polygon, move spikes
578-
if ~obj.isVisible % If it's not visible, can't do cutting here
579-
return;
580-
end
578+
% if ~obj.isVisible % If it's not visible, can't do cutting here
579+
% return;
580+
% end
581581

582582
% Get the potential features to move clusters
583583
curCh = obj.ChannelSelector.Channel;

+nigeLab/+libs/@SpikeImage/Recluster.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ function Recluster(obj)
4141

4242
% Perform clustering
4343
% Use PCA for reclustering, for the time-being
44-
[~,score,~,~,expl,~] = pca(spks);
45-
inspk=[inspk score(:,1:find(cumsum(expl)>95,1))];
44+
% [~,score,~,~,expl,~] = pca(spks);
45+
% inspk = score(:,1:find(cumsum(expl)>95,1));
46+
% inspk=spks;
4647

4748
% [classes,temp] = nigeLab.utils.SPC.DoSPC(par,inspk);
4849
try

+nigeLab/+libs/@SpikeImage/SpikeImage.m

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ function SetVisibleFeatures(obj,clus,val)
316316
return;
317317
end
318318

319-
if ~isfield(obj.Parent,'FeaturesUI')
319+
if ~isprop(obj.Parent.UI,'FeaturesUI')
320320
return;
321321
end
322322

@@ -757,9 +757,29 @@ function GetSpikesToMove(obj,curAxes)
757757
end
758758

759759
% CALLBACK: Execute keyboard shortcut on keyboard button press
760-
function WindowKeyPress(obj,~,evt)
760+
function WindowKeyPress(obj,src,evt)
761761
%WINDOWKEYPRESS Issue different events on keyboard presses
762762
switch evt.Key
763+
case {'n','0'}
764+
if strcmpi(evt.Modifier,'control')
765+
thisClass = obj.Spikes.CurClass;
766+
obj.Spikes.CurClass = 1;
767+
subsetIndex = find(obj.Spikes.Class == thisClass);
768+
evtData = nigeLab.evt.assignClus(subsetIndex,...
769+
obj.Spikes.CurClass,thisClass);
770+
obj.UpdateClusterAssignments(nan,evtData);
771+
else
772+
end
773+
case {'1','2','3','4','5','6','7','8'}
774+
if strcmpi(evt.Modifier,'control')
775+
thisClass = obj.Spikes.CurClass;
776+
obj.Spikes.CurClass = str2num(evt.Key)+1;
777+
subsetIndex = find(obj.Spikes.Class == thisClass);
778+
evtData = nigeLab.evt.assignClus(subsetIndex,...
779+
obj.Spikes.CurClass,thisClass);
780+
obj.UpdateClusterAssignments(nan,evtData);
781+
else
782+
end
763783
case 'space'
764784
obj.ConfirmChanges;
765785
case 'z'

+nigeLab/+utils/getNigelPath.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
switch upper(pathMode)
3636
case 'UNC'
3737
if isunix
38-
nigelPath = nigelPath_;
38+
nigelPath = nigelPath;
3939
else
4040
nigelPath = getUNCPath(nigelPath);
41-
nigelPath = strrep(nigelPath,'\','/');
41+
nigelPath = strrep(nigelPath,'\','/');
4242
end
4343

4444
case ''

+nigeLab/@Block/doSD.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,9 @@
352352
if isempty(artifact)
353353
art = ones(0,5);
354354
else
355-
iStart = artifact([true, diff(artifact) > 1]);
356-
iStop = artifact(fliplr([true, diff(fliplr(artifact)) > 1]));
355+
artifact = artifact(:); % make sure is column oriented
356+
iStart = artifact([true, diff(artifact)' ~= 1]);
357+
iStop = artifact(fliplr([true, diff(fliplr(artifact))' ~= 1]));
357358

358359
artifact = reshape(artifact,numel(artifact),1);
359360

+nigeLab/@Sort/Sort.m

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
% INPUTS
1010
% --------
1111
% nigelObj : (Optional) If not provided, a UI for generation or
12-
% loading of the correct orgExpObj pops up.
12+
% loading of the correct nigelObj pops up.
1313
% Otherwise, the Sort class object parses which
14-
% orgExpObj is given (based on object class), and
14+
% nigelObj is given (based on object class), and
1515
% presents the Sort interface based on that.
1616
%
1717
% --------
@@ -219,7 +219,6 @@ function delete(sortObj)
219219
flag = parseBlocks(sortObj,nigelObj); % Assigns Blocks property
220220
flag = parseAnimals(sortObj,nigelObj); % Assigns Blocks from Animals
221221

222-
flag = setAxesPositions(sortObj); % Draw axes positions
223222

224223
channelName = parseChannelName(sortObj); % Get all channel names
225224
end
@@ -235,4 +234,10 @@ function delete(sortObj)
235234
end
236235
end
237236
% % % % % % % % % % END METHODS% % %
237+
238+
239+
methods (Access = ?nigeLab.libs.SortUI)
240+
flag = setAxesPositions(sortObj); % Draw axes positions
241+
end
242+
238243
end

0 commit comments

Comments
 (0)