Skip to content

Commit 0c799d6

Browse files
author
jaib1
committed
Merge branch 'dev'
2 parents 963f64e + b82ab11 commit 0c799d6

File tree

298 files changed

+79338
-1319
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

298 files changed

+79338
-1319
lines changed

+dat/delParamProfile.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function delParamProfile(expType, profileName)
1515
%remove the params with the field named by profile
1616
profiles = rmfield(profiles, profileName);
1717
%wrap in a struct for saving
18-
set.(expType) = profiles; %#ok<STRNU>
18+
set.(expType) = profiles;
1919

2020
%save the updated set of profiles to each repos
2121
%where files exist already, append

+dat/expLogRequest.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
if nargin < 2
88
args = struct;
9-
elseif nargin == 2 && isstruct(varargin{1});
9+
elseif nargin == 2 && isstruct(varargin{1})
1010
else
1111
args = varargin2struct(varargin{:});
1212
end

+dat/newExp.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function [expRef, expSeq] = newExp(subject, expDate, expParams)
22
%DAT.NEWEXP Create a new unique experiment in the database
3-
% [ref, seq, url] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance])
3+
% [ref, seq] = DAT.NEWEXP(subject, expDate, expParams)
44
% Create a new experiment by creating the relevant folder tree in the
55
% local and main data repositories in the following format:
66
%

+dat/reposPath.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
case {'local' 'l'}
6767
p = paths.localRepository;
6868
otherwise
69-
error('"%s" is not a recognised repository location.', location{1});
69+
error('"%s" is not a recognised repository location.', location);
7070
end
7171

7272
end

+dat/saveParamProfile.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function saveParamProfile(expType, profileName, params)
1717
profiles.(profileName) = params;
1818
%wrap in a struct for saving
1919
set = struct;
20-
set.(expType) = profiles; %#ok<STRNU>
20+
set.(expType) = profiles;
2121

2222
%save the updated set of profiles to each repos
2323
%where files exist already, append

+dat/updateLogEntry.m

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ function updateLogEntry(subject, id, newEntry)
1313
if isfield(newEntry, 'AlyxInstance')
1414
% Update session narrative on Alyx
1515
if ~isempty(newEntry.comments) && ~strcmp(subject, 'default')
16-
newEntry.comments = newEntry.AlyxInstance.updateNarrative(newEntry.comments);
16+
try
17+
newEntry.comments = newEntry.AlyxInstance.updateNarrative(newEntry.comments);
18+
catch
19+
warning('Alyx:updateNarrative:UploadFailed', 'Failed to update Alyx session narrative');
20+
end
1721
end
1822
newEntry = rmfield(newEntry, 'AlyxInstance');
1923
end

+eui/AlyxPanel.m

Lines changed: 166 additions & 119 deletions
Large diffs are not rendered by default.

+eui/ConditionPanel.m

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
classdef ConditionPanel < handle
2+
%CONDITIONPANEL Deals with formatting trial conditions UI table
3+
% Designed to be an element of the EUI.PARAMEDITOR class that manages
4+
% the UI elements associated with all Conditional parameters.
5+
% TODO Add set condition idx
6+
7+
properties
8+
% Handle to UI Table that represents trial conditions
9+
ConditionTable
10+
% Minimum UI Panel width allowed. See also EUI.PARAMEDITOR/ONRESIZE
11+
MinWidth = 80
12+
% Handle to parent UI container
13+
UIPanel
14+
% Handle to UI container for buttons
15+
ButtonPanel
16+
% Handles to context menu items
17+
ContextMenus
18+
end
19+
20+
properties (Access = protected)
21+
% Handle to EUI.PARAMEDITOR object
22+
ParamEditor
23+
% UIControl button for adding a new trial condition (row) to the table
24+
NewConditionButton
25+
% UIControl button for deleting trial conditions (rows) from the table
26+
DeleteConditionButton
27+
% UIControl button for making conditional parameter (column) global
28+
MakeGlobalButton
29+
% UIControl button for setting multiple table cells at once
30+
SetValuesButton
31+
% Indicies of selected table cells as array [row, column;...] of each
32+
% selected cell
33+
SelectedCells
34+
end
35+
36+
methods
37+
function obj = ConditionPanel(f, ParamEditor, varargin)
38+
% FIELDPANEL Panel UI for Conditional parameters
39+
% Input f may be a figure or other UI container object
40+
% ParamEditor is a handle to an eui.ParamEditor object.
41+
%
42+
% See also EUI.PARAMEDITOR, EUI.FIELDPANEL
43+
obj.ParamEditor = ParamEditor;
44+
obj.UIPanel = uix.VBox('Parent', f);
45+
% Create a child menu for the uiContextMenus. The input arg is the
46+
% figure holding the panel
47+
c = uicontextmenu(ParamEditor.Root);
48+
obj.UIPanel.UIContextMenu = c;
49+
obj.ContextMenus = uimenu(c, 'Label', 'Make Global', ...
50+
'MenuSelectedFcn', @(~,~)obj.makeGlobal, 'Enable', 'off');
51+
fcn = @(s,~)obj.ParamEditor.setRandomized(~strcmp(s.Checked, 'on'));
52+
obj.ContextMenus(2) = uimenu(c, 'Label', 'Randomize conditions', ...
53+
'MenuSelectedFcn', fcn, 'Checked', 'on', 'Tag', 'randomize button');
54+
obj.ContextMenus(3) = uimenu(c, 'Label', 'Sort by selected column', ...
55+
'MenuSelectedFcn', @(~,~)obj.sortByColumn, ...
56+
'Tag', 'sort by', 'Enable', 'off');
57+
% Create condition table
58+
p = uix.Panel('Parent', obj.UIPanel, 'BorderType', 'none');
59+
obj.ConditionTable = uitable('Parent', p,...
60+
'FontName', 'Consolas',...
61+
'RowName', [],...
62+
'RearrangeableColumns', 'on',...
63+
'Units', 'normalized',...
64+
'Position',[0 0 1 1],...
65+
'UIContextMenu', c,...
66+
'CellEditCallback', @obj.onEdit,...
67+
'CellSelectionCallback', @obj.onSelect);
68+
% Create button panel to hold condition control buttons
69+
obj.ButtonPanel = uix.HBox('Parent', obj.UIPanel);
70+
% Define some common properties
71+
props.Style = 'pushbutton';
72+
props.Units = 'normalized';
73+
props.Parent = obj.ButtonPanel;
74+
% Create out four buttons
75+
obj.NewConditionButton = uicontrol(props,...
76+
'String', 'New condition',...
77+
'TooltipString', 'Add a new condition',...
78+
'Callback', @(~, ~) obj.newCondition());
79+
obj.DeleteConditionButton = uicontrol(props,...
80+
'String', 'Delete condition',...
81+
'TooltipString', 'Delete the selected condition',...
82+
'Enable', 'off',...
83+
'Callback', @(~, ~) obj.deleteSelectedConditions());
84+
obj.MakeGlobalButton = uicontrol(props,...
85+
'String', 'Globalise parameter',...
86+
'TooltipString', sprintf(['Make the selected condition-specific parameter global (i.e. not vary by trial)\n'...
87+
'This will move it to the global parameters section']),...
88+
'Enable', 'off',...
89+
'Callback', @(~, ~) obj.makeGlobal());
90+
obj.SetValuesButton = uicontrol(props,...
91+
'String', 'Set values',...
92+
'TooltipString', 'Set selected values to specified value, range or function',...
93+
'Enable', 'off',...
94+
'Callback', @(~, ~) obj.setSelectedValues());
95+
obj.ButtonPanel.Widths = [-1 -1 -1 -1];
96+
obj.UIPanel.Heights = [-1 25];
97+
end
98+
99+
function onEdit(obj, src, eventData)
100+
% ONEDIT Callback for edits to condition table
101+
% Updates the underlying parameter struct, changes the UI table
102+
% data. The src object should be the UI Table that has been edited,
103+
% and eventData contains the table indices of the edited cell.
104+
%
105+
% See also FILLCONDITIONTABLE, EUI.PARAMEDITOR/UPDATE
106+
row = eventData.Indices(1);
107+
col = eventData.Indices(2);
108+
assert(all(cellfun(@strcmpi, strrep(obj.ConditionTable.ColumnName, ' ', ''), ...
109+
obj.ParamEditor.Parameters.TrialSpecificNames)), 'Unexpected condition names')
110+
paramName = obj.ParamEditor.Parameters.TrialSpecificNames{col};
111+
newValue = obj.ParamEditor.update(paramName, eventData.NewData, row);
112+
reformed = obj.ParamEditor.paramValue2Control(newValue);
113+
% If successful update the cell with default formatting
114+
data = get(src, 'Data');
115+
if iscell(reformed)
116+
% The reformed data type is a cell, this should be a one element
117+
% wrapping cell
118+
if numel(reformed) == 1
119+
reformed = reformed{1};
120+
else
121+
error('Cannot handle data reformatted data type');
122+
end
123+
end
124+
data{row,col} = reformed;
125+
set(src, 'Data', data);
126+
end
127+
128+
function clear(obj)
129+
% CLEAR Clear all table data
130+
% Clears all trial condition data from UI Table
131+
%
132+
% See also EUI.PARAMEDITOR/BUILDUI, EUI.PARAMEDITOR/CLEAR
133+
set(obj.ConditionTable, 'ColumnName', [], ...
134+
'Data', [], 'ColumnEditable', false);
135+
end
136+
137+
function delete(obj)
138+
% DELETE Deletes the UI container
139+
% Called when this object or its parent ParamEditor is deleted
140+
% See also CLEAR
141+
delete(obj.UIPanel);
142+
end
143+
144+
function onSelect(obj, ~, eventData)
145+
% ONSELECT Callback for when table cells are (de-)selected
146+
% If at least one cell is selected, ensure buttons and menu items
147+
% are enabled, otherwise disable them.
148+
if nargin > 2; obj.SelectedCells = eventData.Indices; end
149+
controls = ...
150+
[obj.MakeGlobalButton, ...
151+
obj.DeleteConditionButton, ...
152+
obj.SetValuesButton, ...
153+
obj.ContextMenus([1,3])];
154+
set(controls, 'Enable', iff(size(obj.SelectedCells, 1) > 0, 'on', 'off'));
155+
end
156+
157+
function makeGlobal(obj)
158+
% MAKEGLOBAL Make condition parameter (table column) global
159+
% Find all selected columns are turn into global parameters, using
160+
% the value of the first selected cell as the global parameter
161+
% value.
162+
%
163+
% See also eui.ParamEditor/globaliseParamAtCell
164+
if isempty(obj.SelectedCells)
165+
disp('nothing selected')
166+
return
167+
end
168+
PE = obj.ParamEditor;
169+
[cols, iu] = unique(obj.SelectedCells(:,2));
170+
names = PE.Parameters.TrialSpecificNames(cols);
171+
rows = num2cell(obj.SelectedCells(iu,1)); %get rows of unique selected cols
172+
cellfun(@PE.globaliseParamAtCell, names, rows);
173+
% If only numRepeats remains, globalise it
174+
if isequal(PE.Parameters.TrialSpecificNames, {'numRepeats'})
175+
PE.Parameters.Struct.numRepeats(1,1) = sum(PE.Parameters.Struct.numRepeats);
176+
PE.globaliseParamAtCell('numRepeats', 1)
177+
end
178+
end
179+
180+
function deleteSelectedConditions(obj)
181+
%DELETESELECTEDCONDITIONS Removes the selected conditions from table
182+
% The callback for the 'Delete condition' button. This removes the
183+
% selected conditions from the table and if less than two conditions
184+
% remain, globalizes them.
185+
%
186+
% See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS
187+
rows = unique(obj.SelectedCells(:,1));
188+
names = obj.ConditionTable.ColumnName;
189+
numConditions = size(obj.ConditionTable.Data,1);
190+
% If the number of remaining conditions is 1 or less...
191+
if numConditions-length(rows) <= 1
192+
remainingIdx = find(all(1:numConditions~=rows,1));
193+
if isempty(remainingIdx); remainingIdx = 1; end
194+
% change selected cells to be all fields (except numRepeats which
195+
% is assumed to always be the last column)
196+
obj.SelectedCells =[ones(length(names),1)*remainingIdx, (1:length(names))'];
197+
%... globalize them
198+
obj.makeGlobal;
199+
else % Otherwise delete the selected conditions as usual
200+
obj.ParamEditor.Parameters.removeConditions(rows);
201+
notify(obj.ParamEditor, 'Changed')
202+
end
203+
% Refresh the table of conditions
204+
obj.fillConditionTable();
205+
end
206+
207+
function setSelectedValues(obj)
208+
% SETSELECTEDVALUES Set multiple fields in conditional table at once
209+
% Generates an input dialog for setting multiple trial conditions at
210+
% once. Also allows the use of function handles for more complex
211+
% values.
212+
%
213+
% Examples:
214+
% (1:10:100) % Sets selected rows to [1 11 21 31 41 51 61 71 81 91]
215+
% @(~)randi(100) % Assigned random integer to each selected row
216+
% @(a)a*50 % Multiplies each condition value by 50
217+
% false % Sets all selected rows to false
218+
%
219+
% See also SETNEWVALS, ONEDIT
220+
PE = obj.ParamEditor;
221+
cols = obj.SelectedCells(:,2); % selected columns
222+
uCol = unique(obj.SelectedCells(:,2));
223+
rows = obj.SelectedCells(:,1); % selected rows
224+
% get current values of selected cells
225+
currVals = arrayfun(@(u)obj.ConditionTable.Data(rows(cols==u),u), uCol, 'UniformOutput', 0);
226+
names = PE.Parameters.TrialSpecificNames(uCol); % selected column names
227+
promt = cellfun(@(a,b) [a ' (' num2str(sum(cols==b)) ')'],...
228+
names, num2cell(uCol), 'UniformOutput', 0); % names of columns & num selected rows
229+
defaultans = cellfun(@(c) c(1), currVals);
230+
answer = inputdlg(promt,'Set values', 1, cellflat(defaultans)); % prompt for input
231+
if isempty(answer) % if user presses cancel
232+
return
233+
end
234+
% set values for each column
235+
cellfun(@(a,b,c) setNewVals(a,b,c), answer, currVals, names, 'UniformOutput', 0);
236+
function newVals = setNewVals(userIn, currVals, paramName)
237+
% check array orientation
238+
currVals = iff(size(currVals,1)>size(currVals,2),currVals',currVals);
239+
if strStartsWith(userIn,'@') % anon function
240+
func_h = str2func(userIn);
241+
% apply function to each cell
242+
currVals = cellfun(@str2double,currVals, 'UniformOutput', 0); % convert from char
243+
newVals = cellfun(func_h, currVals, 'UniformOutput', 0);
244+
elseif any(userIn==':') % array syntax
245+
arr = eval(userIn);
246+
newVals = num2cell(arr); % convert to cell array
247+
elseif any(userIn==','|userIn==';') % 2D arrays
248+
C = strsplit(userIn, ';');
249+
newVals = cellfun(@(c)textscan(c, '%f',...
250+
'ReturnOnError', false,...
251+
'delimiter', {' ', ','}, 'MultipleDelimsAsOne', 1),...
252+
C);
253+
else % single value to copy across all cells
254+
userIn = str2double(userIn);
255+
newVals = num2cell(ones(size(currVals))*userIn);
256+
end
257+
258+
if length(newVals)>length(currVals) % too many new values
259+
newVals = newVals(1:length(currVals)); % truncate new array
260+
elseif length(newVals)<length(currVals) % too few new values
261+
% populate as many cells as possible
262+
newVals = [newVals ...
263+
cellfun(@(a)PE.controlValue2Param(2,a),...
264+
currVals(length(newVals)+1:end),'UniformOutput',0)];
265+
end
266+
ic = strcmp(PE.Parameters.TrialSpecificNames, paramName); % find edited param names
267+
% update param struct
268+
PE.Parameters.Struct.(paramName)(:,rows(cols==find(ic))) = cell2mat(newVals);
269+
% update condtion table with strings
270+
obj.ConditionTable.Data(rows(cols==find(ic)),ic)...
271+
= cellfun(@(a)PE.paramValue2Control(a), newVals', 'UniformOutput', 0);
272+
end
273+
notify(obj.ParamEditor, 'Changed');
274+
end
275+
276+
function sortByColumn(obj)
277+
% SORTBYCOLUMN Sort all conditions by selected column
278+
% If the selected column is already sorted in ascended order then
279+
% the conditions are ordered in descending order instead.
280+
% TODO Sort by multiple columns
281+
% @body currently all conditions are sorted by first selected column
282+
if isempty(obj.SelectedCells)
283+
disp('nothing selected')
284+
return
285+
end
286+
PE = obj.ParamEditor;
287+
% Get selected column name and retrieve data
288+
cols = unique(obj.SelectedCells(:,2));
289+
names = PE.Parameters.TrialSpecificNames(cols);
290+
toSort = PE.Parameters.Struct.(names{1});
291+
direction = iff(issorted(toSort','rows'), 'descend', 'ascend');
292+
[~, I] = sortrows(toSort', direction);
293+
% Update parameters with new permutation
294+
for p = PE.Parameters.TrialSpecificNames'
295+
data = PE.Parameters.Struct.(p{:});
296+
PE.Parameters.Struct.(p{:}) = data(:,I);
297+
end
298+
obj.fillConditionTable % Redraw table
299+
end
300+
301+
function fillConditionTable(obj)
302+
% FILLCONDITIONTABLE Build the condition table
303+
% Populates the UI Table with trial specific parameters, where each
304+
% row is a trial condition (that is, a parameter column) and each
305+
% column is a different trial specific parameter
306+
P = obj.ParamEditor.Parameters;
307+
titles = P.title(P.TrialSpecificNames);
308+
[~, trialParams] = P.assortForExperiment;
309+
if isempty(titles)
310+
obj.ButtonPanel.Visible = 'off';
311+
obj.UIPanel.Visible = 'off';
312+
obj.ParamEditor.Parent.Widths = [-1, 1];
313+
else
314+
obj.ButtonPanel.Visible = 'on';
315+
obj.UIPanel.Visible = 'on';
316+
obj.ParamEditor.Parent.Widths = [-1, -1];
317+
end
318+
data = reshape(struct2cell(trialParams), numel(titles), [])';
319+
data = mapToCell(@(e) obj.ParamEditor.paramValue2Control(e), data);
320+
set(obj.ConditionTable, 'ColumnName', titles, 'Data', data,...
321+
'ColumnEditable', true(1, numel(titles)));
322+
end
323+
324+
function newCondition(obj)
325+
% Adds a new trial condition (row) to the ConditionTable
326+
% Adds new row and populates it with sensible 'default' values.
327+
% These are mostly zeros or empty values.
328+
% See also eui.ParamEditor/addEmptyConditionToParam
329+
PE = obj.ParamEditor;
330+
cellfun(@PE.addEmptyConditionToParam, ...
331+
PE.Parameters.TrialSpecificNames);
332+
obj.fillConditionTable();
333+
end
334+
335+
end
336+
337+
end

0 commit comments

Comments
 (0)