Skip to content

Commit d7ac0f9

Browse files
Merge pull request #172 from emeyers/feature-visual-behavior-datasets
Add support for Visual Behavior datasets
2 parents 4405640 + 8efaf6e commit d7ac0f9

File tree

98 files changed

+9523
-1948
lines changed

Some content is hidden

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

98 files changed

+9523
-1948
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
classdef Item < handle & matlab.mixin.CustomDisplay
2+
3+
% Changes from bot.item.internal.abstract.Item:
4+
% - Simplified constructor
5+
% - Added method for assigning manifest
6+
% - Changed display of non-scalar objects
7+
8+
%% PROPERTIES
9+
properties (SetAccess = public)
10+
id; % ID of this item
11+
info; % Struct containing info about this item
12+
end
13+
14+
%% HIDDEN PROPERTIES
15+
properties (Hidden, Access = protected)
16+
manifest; % Handle to pertinent manifest containing all available Items of this class
17+
end
18+
19+
properties (Abstract, Hidden, Access = public, Constant)
20+
DATASET (1,1) bot.item.internal.enum.Dataset
21+
DATASET_TYPE (1,1) bot.item.internal.enum.DatasetType
22+
ITEM_TYPE (1,1) bot.item.internal.enum.ItemType
23+
end
24+
25+
properties (Abstract, SetAccess = protected, Hidden)
26+
CORE_PROPERTIES (1,:) string;
27+
LINKED_ITEM_PROPERTIES (1,:) string;
28+
end
29+
30+
properties (Hidden, SetAccess=protected, GetAccess = protected)
31+
ITEM_INFO_VALUE_PROPERTIES (1,:) string = string.empty(1,0);
32+
LINKED_ITEM_VALUE_PROPERTIES (1,:) string = string.empty(1,0);
33+
end
34+
35+
%% CONSTRUCTOR
36+
methods
37+
38+
function obj = Item(itemIDSpec)
39+
40+
arguments
41+
itemIDSpec {bot.item.internal.abstract.Item.mustBeItemIDSpec} = [];
42+
end
43+
44+
% No Input Argument Constructor Requirement
45+
if nargin == 0 || isempty(itemIDSpec)
46+
return;
47+
end
48+
49+
numItems = countItems(itemIDSpec); % Local function;
50+
51+
if numItems > 1
52+
itemClass = class(obj);
53+
obj(numItems) = feval(itemClass);
54+
55+
for idx = 1:numItems
56+
if istable(itemIDSpec)
57+
obj(idx) = feval(itemClass, itemIDSpec(idx, :));
58+
else
59+
obj(idx) = feval(itemClass, itemIDSpec(idx));
60+
end
61+
end
62+
return
63+
end
64+
65+
obj.assignManifest()
66+
67+
% Identify the manifest table row(s) associated to itemIDSpec
68+
if istable(itemIDSpec)
69+
manifestTableRow = itemIDSpec;
70+
elseif isnumeric(itemIDSpec)
71+
manifestTableRow = obj.findManifestTableRow(itemIDSpec);
72+
else
73+
assert(false);
74+
end
75+
76+
assert(~isempty(manifestTableRow), "BOT:Item:idNotFound", ...
77+
"Specified numeric ID not found within manifest(s) of all available Items of class %s", mfilename('class'));
78+
79+
% - Assign the table data to the metadata structure
80+
obj.info = table2struct(manifestTableRow);
81+
obj.id = obj.info.id;
82+
83+
obj.initLinkedItems()
84+
end
85+
end
86+
87+
methods (Access = protected)
88+
function initLinkedItems(obj)
89+
% Subclasses may implement
90+
end
91+
92+
function str = getItemAnnotation(obj)
93+
% getItemAnnotation - Provide a custom item annotation for display
94+
% purposes.
95+
str = missing;
96+
end
97+
end
98+
99+
methods (Access = private)
100+
101+
function assignManifest(obj)
102+
% assignManifest - Assign item manifest for specified object.
103+
datasetName = obj.DATASET;
104+
datasetType = obj.DATASET_TYPE;
105+
106+
if datasetName == bot.item.internal.enum.Dataset.VisualBehavior
107+
manifestClassName = sprintf("%s%sManifest", datasetName, datasetType);
108+
fullManifestClassName = sprintf('bot.internal.metadata.%s.instance', manifestClassName);
109+
elseif datasetName == bot.item.internal.enum.Dataset.VisualCoding
110+
manifestClassName = sprintf("%sManifest", datasetType);
111+
fullManifestClassName = sprintf('bot.item.internal.%s.instance', manifestClassName);
112+
end
113+
114+
obj.manifest = feval(fullManifestClassName);
115+
end
116+
end
117+
118+
methods
119+
function datasetType = getDatasetType(obj)
120+
datasetType = char(obj.DATASET_TYPE);
121+
end
122+
123+
function datasetName = getDatasetName(obj)
124+
datasetName = char(obj.DATASET);
125+
end
126+
end
127+
128+
methods (Access = protected) % Subclasses may override
129+
function tableRow = findManifestTableRow(obj, itemId)
130+
131+
itemId = uint32(round(itemId)); % Ensure ID is correct type
132+
133+
manifestTable = obj.manifest.getItemTable(obj.ITEM_TYPE);
134+
matchingRow = manifestTable.id == itemId;
135+
tableRow = manifestTable(matchingRow, :);
136+
end
137+
end
138+
139+
%% HIDDEN METHODS SUPERCLASS IMPLEMENTATION (matlab.mixin.CustomDisplay)
140+
methods (Hidden, Access = protected)
141+
142+
function str = getHeader(obj)
143+
str = [email protected](obj);
144+
str = replace(str, 'with properties', sprintf('(%s) with properties', obj(1).getDatasetName()));
145+
end
146+
147+
function groups = getPropertyGroups(obj)
148+
if ~isscalar(obj)
149+
groups = [email protected](obj);
150+
else
151+
152+
% Core properties
153+
mc = metaclass(obj);
154+
dcs = [mc.PropertyList.DefiningClass];
155+
corePropsLocal = findobj(mc.PropertyList(string({dcs.Name}) == mfilename('class')),'GetAccess','public','-and','Hidden',false);
156+
groups(1) = matlab.mixin.util.PropertyGroup([corePropsLocal.Name obj.CORE_PROPERTIES]);
157+
158+
% Derived properties from Info
159+
if ~isempty(obj.ITEM_INFO_VALUE_PROPERTIES)
160+
groups(end+1) = matlab.mixin.util.PropertyGroup(obj.ITEM_INFO_VALUE_PROPERTIES, 'Info Derived Values');
161+
end
162+
163+
% Linked item tables
164+
groups(end+1) = matlab.mixin.util.PropertyGroup(obj.LINKED_ITEM_PROPERTIES, 'Linked Items');
165+
166+
% Derived properties from Linked Item Tables
167+
if ~isempty(obj.LINKED_ITEM_VALUE_PROPERTIES)
168+
groups(end+1) = matlab.mixin.util.PropertyGroup(obj.LINKED_ITEM_VALUE_PROPERTIES, 'Linked Item Derived Values');
169+
end
170+
end
171+
end
172+
173+
function displayNonScalarObject(obj)
174+
%TODO: Refactor to use String, if keeping this nonscalar display format
175+
176+
% - Only display limited data
177+
% arr_size = size(obj);
178+
% size_str = sprintf("%d×", arr_size(1:end-1)) + sprintf("%d", arr_size(end));
179+
%
180+
% class_name = strsplit(class(obj), '.');
181+
% class_name = class_name{end};
182+
% class_name_part = sprintf('<a href="matlab:helpPopup %s">%s</a>', class(obj), class_name);
183+
%
184+
% fprintf(" %s %s array\n", size_str, class_name_part);
185+
%
186+
% ids_part = "[" + sprintf('%d, ', [obj(1:end-1).id]) + sprintf('%d]', obj(end).id);
187+
%
188+
% fprintf(' ids: %s\n', ids_part);
189+
190+
numObjects = numel(obj);
191+
stringRep = cell(1, numObjects);
192+
193+
for i = 1:numObjects
194+
stringRep{i} = sprintf(' %s (%d) of type "%s"', ...
195+
string(obj(i).ITEM_TYPE), obj(i).id, obj(i).SessionType);
196+
% Todo: Is session type available for cells, probes etc?
197+
198+
annotation = obj(i).getItemAnnotation();
199+
if ~ismissing(annotation)
200+
stringRep{i} = sprintf('%s [%s]', stringRep{i}, annotation);
201+
end
202+
end
203+
204+
str = obj.getHeader;
205+
str = strrep(str, ' with properties:', '');
206+
disp(str)
207+
fprintf( '%s\n\n', strjoin(stringRep, ' \n') );
208+
end
209+
end
210+
211+
%% HIDDEN METHODS - STATIC
212+
methods (Hidden, Static)
213+
function mustBeItemIDSpec(val)
214+
%MUSTBEITEMIDSPEC Validation function for items specified to BOT item factory functions for item object array construction
215+
216+
eidTypePrefix = "mustBeBOTItemId:";
217+
eidTypeSuffix = "";
218+
msgType = "";
219+
220+
if istable(val)
221+
222+
eidTypeSuffix = "invalidItemTable";
223+
224+
if ~any(ismember(val.Properties.VariableNames, 'id')) && ~any(ismember(val.Properties.VariableNames, 'behavior_session_id'))
225+
msgType = "Table supplied not recognized as a valid BOT Item information table";
226+
end
227+
228+
% if height(val) ~= 1
229+
% msgType = "Table supplied must have one and only one row";
230+
% end
231+
232+
elseif ~isnumeric(val) || ~isvector(val) || ~all(isfinite(val)) || any(val<=0)
233+
eidTypeSuffix = "invalidItemIDs";
234+
msgType = "Must specify BOT item object(s) to create with either a numeric vector of valid ID values or a valid Item information table";
235+
elseif ~isinteger(val) && ~all(round(val)==val)
236+
eidTypeSuffix = "invalidItemIDs";
237+
msgType = "Must specify BOT item object(s) to create with either a numeric vector of valid ID values or a valid Item information table";
238+
end
239+
240+
241+
% Throw error
242+
if strlength(msgType) > 0
243+
throwAsCaller(MException(eidTypePrefix + eidTypeSuffix,msgType));
244+
end
245+
end
246+
247+
function tf = isItemIDSpecScalar(itemIDSpec)
248+
if istable(itemIDSpec)
249+
tf = height(itemIDSpec) == 1;
250+
else
251+
tf = numel(itemIDSpec) == 1;
252+
end
253+
end
254+
end
255+
256+
% methods (Static)
257+
%
258+
% function tbl = removeUnusedCategories(tbl)
259+
% % TODO: Consider if it's a desired behavior for category lists to be narrowed for linked item tables? Or better to retain the "global" view of all available in the container session?
260+
%
261+
% if isempty(tbl)
262+
% return;
263+
% end
264+
%
265+
% varTypes = string(cellfun(@class,table2cell(tbl(1,:)),'UniformOutput',false));
266+
% varNames = string(tbl.Properties.VariableNames);
267+
%
268+
% catVarIdxs = find(varTypes.matches("categorical"));
269+
%
270+
% for idx = catVarIdxs
271+
% varName = varNames(idx);
272+
%
273+
% validCats = unique(tbl.(varName));
274+
% allCats = categories(tbl{1,varName});
275+
% invalidCats = setdiff(allCats,validCats);
276+
%
277+
% tbl.(varName) = removecats(tbl.(varName),invalidCats);
278+
%
279+
% end
280+
%
281+
% end
282+
% end
283+
284+
end
285+
286+
function nItems = countItems(itemIDSpec)
287+
if istable(itemIDSpec)
288+
nItems = height(itemIDSpec);
289+
else
290+
nItems = numel(itemIDSpec);
291+
end
292+
end

0 commit comments

Comments
 (0)