Skip to content

Commit 120e790

Browse files
committed
Adds events, PCA control, and improved 3D plotting/animation
Adds persistent event storage and an addEvents API so behavioral events can be saved with proper validation. Uses the explicit PCA size property for embeddings instead of deriving a cap from active channels, giving users direct control over dimensionality. Enhances 3D visualization: plot3 now accepts a max-traces parameter, selects representative trajectories deterministically (improving reproducibility) instead of random sampling, and fixes trial-time formatting. Introduces animate3 to animate trajectories in 3D for better inspection of dynamics. Minor class-property reorganization to accommodate the new state fields and remove a placeholder method. These changes improve usability, reproducibility, and visualization of neural embeddings.
1 parent c3c26c7 commit 120e790

2 files changed

Lines changed: 103 additions & 12 deletions

File tree

@NeuralEmbedding/NeuralEmbedding.m

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787
nTrial double
8888
nArea double
8989
nCondition double
90+
91+
currentEmbeddingMethod string = ""
9092
end
9193

9294
properties (Access = private)
@@ -101,15 +103,15 @@
101103
M_ = ...
102104
struct('type',[],'date',[],'condition',... % Metrics struct storing quality matrics.
103105
[],'data',[],'Area',[]);
104-
106+
Evts_ = ...
107+
struct('Name',[],'Area',[],'Idx',[]);
105108
W_ cell % projection matrix
106109
Winv_ cell % inverse projection matrix
107110
mu double = 0 % per unit mean
108111
ss double = 1 % per unit variance
109112
subsampling double = 1 % subsampling, it is updated after binning
110113
homogeneous logical = false % flag for trial homogenuity. If all trials all equally long, this is 0
111114

112-
currentEmbeddingMethod string = ""
113115
VarExplained_ double % cell storing variance explained values
114116
numPC double = 6
115117
end
@@ -240,6 +242,18 @@ function performPrePro(obj)
240242
% Z-score the data
241243
obj.zscoreData();
242244
end
245+
246+
function addEvents(obj,evts)
247+
248+
CorrectedEvts = evts(:);
249+
ff = fieldnames(evts);
250+
if not(all(ismember(ff,["Name","Area","Idx"])))
251+
error("Event structure must contain the fields Namr, Area, and Idx");
252+
end
253+
254+
obj.Evts_ = [obj.Evts_;CorrectedEvts]
255+
256+
end
243257
end
244258

245259
%% Dependent methods
@@ -852,10 +866,6 @@ function zscoreData(obj)
852866
%% Compute embeddings
853867
methods (Access=public)
854868
flag = findEmbedding(obj,type,Area)
855-
856-
function flag = addEvents(obj,evt)
857-
% TODO add behavioural events to be stored in a trial by trial base
858-
end
859869
end
860870

861871
%% Compute manifold metrices
@@ -865,20 +875,25 @@ function zscoreData(obj)
865875

866876
%% Plot data
867877
methods
868-
function plot3(obj)
878+
function plot3(obj,maxT)
879+
if nargin < 2
880+
maxT = 40;
881+
end
882+
869883
if not(isscalar(obj))
870-
arrayfun(@(o)o.plot3,obj);
884+
arrayfun(@(o)o.plot3(maxT),obj);
871885
return;
872886
end
873887
reducedE = cellfun(@(x)[x(1:3,:) nan(3,1)], ...
874888
obj.E, ...
875889
'UniformOutput',false);
876-
t = cellfun(@(t)[t' nan], ...
890+
t = cellfun(@(t)[t(:)' nan], ...
877891
obj.TrialTime, ...
878892
'UniformOutput',false);
879893
nT = sum(obj.cMask);
880-
MaxLines = min(nT,80);
881-
idx = randperm(nT,MaxLines);
894+
MaxLines = min(nT,maxT);
895+
% idx = randperm(nT,MaxLines);
896+
idx = findClosestN(reducedE,MaxLines);
882897
reducedE = [reducedE{idx}];
883898
t = [t{idx}];
884899
figure;
@@ -892,9 +907,28 @@ function plot3(obj)
892907
title(obj.Animal + " " +obj.Session)
893908
xlabel('Dimension 1');ylabel('Dimension 2');zlabel('Dimension 3');
894909
colorbar
910+
911+
function idx = findClosestN(traj,N)
912+
m = median(cat(3,traj{:}),3);
913+
% [dist,idx] = sort(cellfun(@(l) ...
914+
% norm(l(:,1:end-1) - m(:,1:end-1)),traj));
915+
[dist,idx] = sort( ...
916+
cellfun(@(l) ...
917+
max(sum(l(:,1:end-1) - m(:,1:end-1),2)), ...
918+
traj) ...
919+
);
920+
idx = idx(1:N);
921+
end
922+
923+
895924
end
896925

897926
function peth(obj)
927+
if not(isscalar(obj))
928+
arrayfun(@(o)o.peth,obj);
929+
return;
930+
end
931+
898932
if not(obj.homogeneous)
899933
warning("PETH is not available for Dishomogeneous data.%sAborting.",newline);
900934
return;
@@ -981,6 +1015,63 @@ function peth(obj)
9811015
obj.cMask = BakCond;
9821016
obj.aMask = BakArea;
9831017
end
1018+
1019+
function animate3(obj,maxT)
1020+
if nargin < 2
1021+
maxT = 40;
1022+
end
1023+
1024+
if not(isscalar(obj))
1025+
warning("Array object ont yet supported.")
1026+
return;
1027+
end
1028+
reducedE = cellfun(@(x)[x(1:3,:) nan(3,1)], ...
1029+
obj.E, ...
1030+
'UniformOutput',false);
1031+
t = cellfun(@(t)[t(:)' nan], ...
1032+
obj.TrialTime, ...
1033+
'UniformOutput',false);
1034+
nT = sum(obj.cMask);
1035+
MaxLines = min(nT,maxT);
1036+
% idx = randperm(nT,MaxLines);
1037+
idx = findClosestN(reducedE,MaxLines);
1038+
reducedE = reducedE(idx);
1039+
AxLimits = [min([reducedE{:}],[],2),max([reducedE{:}],[],2)];
1040+
1041+
t = t(idx);
1042+
ax = axes(figure);
1043+
ax.set("XLim", AxLimits(1,:),"YLim",AxLimits(2,:),"ZLim",AxLimits(3,:));
1044+
for tr = 1:numel(t)
1045+
anl(tr) = animatedline(ax,reducedE{tr}(1,1),reducedE{tr}(2,1),reducedE{tr}(3,1));
1046+
anl(tr).MaximumNumPoints = 50;
1047+
end
1048+
1049+
for tt = 1:numel(t{1})
1050+
for tr = 1:numel(t)
1051+
anl(tr).addpoints(reducedE{tr}(1,tt),reducedE{tr}(2,tt),reducedE{tr}(3,tt));
1052+
% title(sprintf("%d",t(tt)));
1053+
pause(1/1500);
1054+
end
1055+
end
1056+
% title(obj.Animal + " " +obj.Session)
1057+
% xlabel('Dimension 1');ylabel('Dimension 2');zlabel('Dimension 3');
1058+
% colorbar
1059+
1060+
function idx = findClosestN(traj,N)
1061+
m = median(cat(3,traj{:}),3);
1062+
% [dist,idx] = sort(cellfun(@(l) ...
1063+
% norm(l(:,1:end-1) - m(:,1:end-1)),traj));
1064+
[dist,idx] = sort( ...
1065+
cellfun(@(l) ...
1066+
max(sum(l(:,1:end-1) - m(:,1:end-1),2)), ...
1067+
traj) ...
1068+
);
1069+
idx = idx(1:N);
1070+
end
1071+
1072+
1073+
end
1074+
9841075
end
9851076

9861077
%% Class data preview

@NeuralEmbedding/findEmbedding.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
parNames = ["subsampling","numPC","TrialL"];
4545
% Get the parameters for this algorithm
4646
pars = obj.assignEPars(parNames,type);
47-
pars.numPC = min(sum(obj.aMask),30);
47+
pars.numPC = obj.NumPC;
4848
pars.projectOnly = projectOnly;
4949
try
5050
% GPFA does not need presmoothing. It gets as input P instead of S

0 commit comments

Comments
 (0)