Skip to content

Startup_Shortcuts

Nabarb edited this page May 23, 2022 · 4 revisions

nigeLab Shortcuts

Easier indexing to prevent cumbersome array references and iterations.

Summary

By exploiting the subsref methods of Tank, Animal and Block it is possibile to use the {} operator to quickly access the needed nigelObjs without having to navigate the full data structure. That means that you can:

  • access Block and Animal objects directly from the Tank level
  • access the needed data directly from the block
  • filter Blocks and Animals by Metadata or Keys

Standard numerical indexing

Use {} brackets to access hierarchical elements more easily.

tankObj

  • A = tankObj{aa}; is equivalent to A = tankObj.Children(aa);
  • B = tankObj{aa,bb}; is equivalent to B = tankObj.Children(aa).Children(bb);

Note: aa and bb can be arrays. In that case bb is used to index the animals selected with aa.

Example 1

aa = 1:3;
bb = [2 5];
Bs1 =  tankObj{aa,bb};

Bs2 = [];
As= tankObj{aa};
for a = 1:numel(As)
   Bs2 = [Bs2  As(a).Children(bb)];
end

Bs1 and Bs2 are equivalent. They contain the 2nd and 5th Blocks of the first three Animals of tankObj

Note: the keyword end is not available with {} indexing, while : can be used.

animalObj

nigeLab.Animal changes its behavior depending on if it is a scalar or vector object

Scalar case

Only the use of single-index with {} is allowed for scalar objects.

  • If nigeLab.Animal is scalar, then using a single index returns the corresponding element of the .Children (array) property of "Child" nigelObj objects.

Example 2

Return first Block directly from Animal using numeric indexing.

bb = 1;
B1 = animalObj{bb};
B2 = animalObj.Children(bb);
  • B1 and B2 are equivalent.

Example 3

Don't do this, please.

bb = 1;
cc = 1:64; % Channels? Who knows
B = animalObj{bb,cc}; % This will throw a warning
  • Issues a warning and ignores all index arguments except bb (the first)
    • This effectively makes the previous statement equivalent to:
bb = 1;
B = animalObj.Children(bb);

Vector Case

Use of {} with vector nigelObj objects requires two indexing subscripts.

  • If nigeLab.Animal is a vector, then two indexing subscript arguments are required.
    • The first subscript argument addresses the Animal object(s) to be accessed in the array.
    • The second subscript argument references the .Block object(s) from the .Children (array) property of the Animal objects returned by the first argument.
  • This makes it functionally equivalent to the tankObj syntax, see the examples below.

Example 4

Return the first three Block objects from the second and fourth Animal in the array.

aa = [2,4]; % Animal indices from array
bb = 1:3;   % Block indices (must have at least 3 in .Children)
% Standard "old-fashioned" way:
blockArray1 = animalObj(aa).Children(bb); 
% which will throw an error due to the fact that the . operator doesn't really get along with arrays

% Nigel syntax:
blockArray2 = animalObj{aa,bb};
% Alternatively, if animalObj is the full set of tankObj.Children: 
blockArray3 = tankObj{aa,bb};
  • The objects returned by blockArray1, blockArray2, and blockArray3 are equivalent.

** NOTE: **
B = animalObj{bb}; is unsupported for nigeLab.Animal arrays.

  • To access nigeLab.Animal indexed by bb from an array:
A = animalObj(2); % Second animal in animalObj array

Advanced numerical indexing

Asymmetric indexing

{} allows asymmetric indexing, which applies different index subscripts to each element in an array.

  • Each returned element is contained in a cell array.
  • The second argument must be a cell array, with the same number of cells as indices in the first subscript argument.

Example 5

Return the first Block in the .Children property of tankobj.Children(1)

  • Simultaneously, return the first two Block objects in the .Children property of tankObj.Children(2)
    • Note: Returned array B could be: empty, scalar, or [1 x 2] nigeLab.Block array.
B = tankObj{[1,2],{1,[1,2]}}; 

is equivalent to

b1 = tankObj{1,1};
b2 =  tankObj{2,1:2};
B = [b1 b2];

Key-based Indexing

Use 'Key' in place of numeric indexing subscript arguments.

  • Indices (aa and bb in examples) do not need to be numeric.
  • 'Public' field of 'Key' property struct can be used equivalently.
    • 'Key': Random "unique" alphanumeric char array (starts with alphabetical element always)
    • For example, you could use a numeric vector for the first index and a 'Key' as the second index.
    • To address multiple objects at once using 'Key'-indexing, assign each 'Key' as a cell array element.
  • Note: Unlike numeric-indexing, 'Key'-indexing will not throw an error if an "out-of-range" key is requested.
    • If a Block 'Key' has no valid matches, the Block corresponding to it is returned as an empty Block object.

Example 6

Return (scalar) Block that matches key1, searching all elements of .Children of tankObj.Children([1,2]).

aa=[1,2];
bb=key1; % e.g. key1 = getKey(blockObj1,'Public');
B = tankObj{aa,bb};

Example 7

Return the Block object or array for elements matching key1 or key2 in the .Children property of tankObj.Children([1,2])

...
% Processing returns key1, key2
% Keys to two blocks of interest
...
aa = [1,2];
bb={key1,key2};
B = tankObj{aa,bb};

Example 8

Use 'Key'-indexing instead of numeric indexing for asymmetric access.

B = tankObj{[1,2], {key1, {key2 key3} } };
  • This looks for a match to key1 among the Block objects in the .Children property of tankObj.Children(1)
  • Simultaneously, this looks for Block objects with matches for key2 and key3 in the Block objects in the array .Children of tankObj.Children(2)

Example 9

You can go crazy with this apparently. (FB had too much fun it looks like)

B = tankObj{ {keya1, keya2} , {keyb1, {keyb2 keyb3} } };
  • Technically, this is supported, but it probably needs more testing and may lead to unexpected results.

Example 10

Don't do this, please. Please.

B = tankObj{ [1,2], {1, {key1 key2} } };

Mixing numeric and 'Key'-based indexing for a single index argument is not supported.

Meta-based Indexing

Use the Metadata stored in the .Meta property to select the correct Animal or Block object(s)

  • Metadata filtering follows matlab's Name-Value comma-separated syntax.
  • If no field with the given name is present in the .Meta struct, an Unrecognized field name error is raised.
  • When more then one filter is included (i.e. more then one Name-Value pair), they are concatenated with a logical &.
  • When more then one filter is included (i.e. more then one Name-Value pair), they need to be wrapped in cell array.
  • This indexing can be applied to arrays of nigelObj as well. In this case the array is treated equally to a single object on the above level.

Example 11###

A = tankObj{'Species','Rat'};
allAnimals = tankObj.Children;
A = allAnimals {'Species','Rat'};

A2 = tankObj{{'Species','Rat','ElectrodeLayout','X'}};
A2 = allAnimals{{'Species','Rat','ElectrodeLayout','X'}};

In A we have all animals with a field .Meta.Species == 'Rat', while in A2 we have all the rats that have the ElectrodeLayout 'X'.

Note! Nigel doesn't like when the .Meta strutures are not uniform. All Animal objects should have the same fields in .Meta and all Block objects should have the same fields in .Meta.

You can go on and index the blocks as well, directly from the tank level.

Example 12###

B = tankObj{{'Species','Rat'},{'RecDate','19700101'}};

This will return in B all the Block objects with 'RecDate' equals to '19700101' collected from all the Animal objects wirh 'Species' equal to 'Rat'. Asymmetric indexing is not supperted for Meta-based indexing (yet).

Special blockObj indexing###

We gazed far too long into the abyss

  • By using {} in conjunction with nigeLab.defaults.Shortcuts, it is possible to directly reference Block elements two levels below the Block hierarchical level.
    • This makes it convenient to work with various fields such as nigeLab.Block.Channels.Spikes, which have extra '.' elements and long, cumbersome names for weary fingers to type.

Configuring Shortcuts

default.Shortcuts now has a new format.

  • default.Shortcuts now returns a struct. Each field of the returned struct is a shortcut keyword.
    • Each keyword struct field contains a secondary struct with two fields:
      • subfields: A cell array of char arrays, each starting with a FieldType (e.g. 'Channels') and listing in order the '.'-indexed fields to get to the Shortcut.
      • indexable: A logical array that must have the same number of elements as its corresponding subfields entry. Each logical element indicates that its corresponding subfields entry requires ()-based numeric indexing (if true) or not.

Example 13

Access the first 100 samples in the 'Raw' data of the channel iCh

% Typical, boring "hooman" way:
snippet = blockObj.Channels(iCh).Raw(1:100);

By configuring ~/+nigeLab/+defaults/Shortcuts.m, we can make the same reference shorter.

  • In the +defaults file, add:
pars.x.subfields = {'Channels', 'Raw'};
pars.x.indexable = [true      , true];
  • Next, run:
% Load new Shortcuts parameters directly from +defaults file
updateParams(blockObj,'Shortcuts','Direct');
  • Now, the Shortcut should work:
% Typical, boring "hooman" way:
snippet1 = blockObj.Channels(iCh).Raw(1:100);
% Exciting, superior "Nigel" way:
snippet2 = blockObj{'x',iCh,1:100};
  • In this example, snippet1 and snippet2 are equivalent.
    • Notice that the Shortcut keyword is the same as the corresponding field in the Shortcuts struct, but does not need to correspond to any existing field in the Block object or relate in any way to its Fields.

Example 14

Use Shortcuts to access samples of a digital stream.

% Inferior "hooman" method:
snippet = blockObj.Streams.DigIO(iCh).data(1:100);
  pars.io.subsfield = {'Streams', 'DigIO', 'data'};
  pars.io.indexable = [false    , true   , true];
  • Notice that 'Streams' is not ()-indexed. In any reference to 'Streams', it is strictly .-indexed.
    • e.g. value = blockObj.Streams.(streamsField).(substreamsField);

After updating Shortcuts parameters (see above) 'Streams.DigIO' can be referenced more easily:

k = getStreamIndex(blockObj,'trial-running');
% Carpal-tunnel-inducing "hooman" method:
snippet1 = blockObj.Streams.DigIO(k).data(1:100);
% Undeniably optimal Nigel way:
snippet2 = blockObj{'io',k,1:100};
  • The returned values of snippet1 and snippet2 are equivalent.

Direct indexing

Use even more compact indexing syntax

Example 15

Use two ()-based indices instead of three:

% For Nigel, when he is feeling sensible:
snippet1 = blockObj.Channels(iCh).Raw(1:100);
% For Nigel, when he is feeling fancy:
snippet2 = blockObj.raw(iCh,1:100);
  • The returned values of snippet1 and snippet2 are equivalent.
    • (See example re: 'x' above for explanation of setting up this style of shortcut for 'raw')

Advanced Array Indexing

Warning: Advanced indexing is only available in {}, not using direct indexing!

Symmetric Array Indexing

Return identically indexed elements using a single array.

  • This may be convenient when you are accessing something like Raw data, which should have an identical number of samples for each amplifier channel of a given recording (Block).

Example 16

Return the first ten samples from the first two channels.

% Infuriatingly verbose "hooman" method:
snippet1 = nan(2,10);
for iCh = 1:2
   snippet1(iCh,:) = blockObj.Channels(iCh).Raw.data(1:10);
end

% Refined, elegant Nigel way:
snippet2 = blockObj{'raw',[1,2], 1:10};
  • The values returned in snippet1 and snippet2 are identical.

Asymmetric Array Indexing

Access different indices in different channels using curly brackets.

  • This makes it possible to return different numbers of elements from an array.
    • This is particularly useful, for example, if you think different Channels sub-fields should return different numbers of elements based on indexing derived elsewhere.

Example 17

Return the first sample from channel 1, and the first two samples from channel 2.

% Nigel would be unlikely to use it this way:
C = blockObj{'raw',[1,2],{1,[1,2]}};
  • This returns a cell array in C, which the first sample from channel 1 in its first cell and the first two samples from channel 2 in its second cell.

Example 18

% Nigel would do this though:
C = blockObj{'spikes',[1,2],{1,[1,2]}}; % Spikes likely to have different # per channel
  • This returns a cell array in C, which contains the first spike of channel 1 in its first cell and the first two spikes of channel 2 its second cell.

Warning

Note: The operator end is not available in shortcuts!

Clone this wiki locally