Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions +hw/calibrate.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,16 @@

% Check inputs
narginchk(5,15)
assert(~mod(length(varargin),2), 'Rigbox:hw:calibrate:partialPVpair', ...
'Incorrect number of Name-Value pairs')
if ~all(cellfun(@ischar, (varargin(1:2:end)))) || mod(length(varargin),2)
error('Rigbox:hw:calibrate:nameValueMismatch', ...
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose the id partialPVpair because that's exactly the one MATLAB used in a builtin function. Whether they themselves have standardized all their IDs moot (they probably don't).

Copy link
Copy Markdown
Member Author

@jkbhagatio jkbhagatio Sep 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. I use nameValueMismatch in my own code. That's fine, feel free to make any changes you see fit before merging (rebasing onto dev and squash-merging)

['If using input arguments, %s requires them to be constructed '...
'in name-value pairs'], mfilename);
end
% Check that the scale is initialized
if isempty(scales.Port) || isempty(scales.readGrams)
error('Rigbox:hw:calibrate:noscales', ...
'Unable to communicate with scale. Scales object is not properly initialized')
['Unable to communicate with scale. Scales object is not '...
'properly initialized'])
end

% Parse Name-Value pairs
Expand All @@ -55,10 +59,10 @@
inputs = cell2struct(varargin(2:2:end)', varargin(1:2:end)');
p = mergeStructs(inputs, defaults);

signalGen = rewardController.SignalGenerators(strcmp(rewardController.ChannelNames, channel));
signalGen =...
rewardController.SignalGenerators(strcmp(rewardController.ChannelNames,...
channel));
t = meshgrid(linspace(tMin, tMax, p.nVolumes), zeros(1, p.nPerT));
% t = [10 25 50 100 200 500 1000]/1000;
% t = [1000]/1000;
n = repmat(p.delivPerSample, size(t));
dw = zeros(size(t));

Expand All @@ -78,13 +82,15 @@
newWeight = scales.readGrams;
assert(newWeight > prevWeight + 0.02, ...
'Rigbox:hw:calibrate:deadscale',...
'Error: Scale is not registering changes in weight. Confirm scale is properly connected and that water is landing into the dish')
['Error: Scale is not registering changes in weight. Confirm scale is \n'...
'properly connected and that water is landing into the dish']);

prevWeight = newWeight;
fprintf('Initial scale reading is %.2fg\n', prevWeight);

startTime = GetSecs;
fprintf('Deliveries will take approximately %.0f minute(s)\n', ceil(approxTime/60));
fprintf('Deliveries will take approximately %.0f minute(s)\n',...
ceil(approxTime/60));

try
for j = 1:size(t,2)
Expand All @@ -96,7 +102,8 @@
dw(i,j) = newWeight - prevWeight;
prevWeight = newWeight;
ml = dw(i,j)/n(i,j);
fprintf('Weight delta = %.2fg. Delivered %ful per %fms\n', dw(i,j), 1000*ml, 1000*t(i,j));
fprintf('Weight delta = %.2fg. Delivered %ful per %fms\n',...
dw(i,j), 1000*ml, 1000*t(i,j));
end
end
catch ex
Expand Down
116 changes: 116 additions & 0 deletions +hw/checkRewardValveCalibration.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
function checkRewardValveCalibration(varargin)
% HW.CHECKREWARDVALVECALIBRATION delivers a set amount of reward to check reward valve calibration
%
% This function is used to deliver a set amount of reward via a reward
% valve controlled by a NI-DAQ: a user should check the amount of water
% delivered manually by catching the delivered reward via a falcon tube or
% similar container.
%
% Inputs:
% `amt` (numeric): The amount (in mL) of water to deliver. (default =
% 5.0)
% `chan` (string): The channel of the NI-DAQ associated with the reward
% valve. If not given, `chan` is found by getting the channel
% associated with the `hw.RewardValveControl` object in the rig's
% `hw.DaqController` object.
%
% Examples:
% - Check that 5 mL of water is delivered:
% `hw.checkRewardValveCalibration()`
% - Check that 10 mL of water is delivered via 'ao1':
% `hw.checkRewardValveCalibration('amt', 10.0, 'chan', 'ao1')`
%
% See also: `hw.calibrate`, `hw.RewardValveControl`
%
% Todo: Sanitize and integrate into `hw.RewardValveControl`
% Todo: Create appropriate tests using mock objects.

%% Set input arg values and get `hw.RewardValveControl` object from which to deliver reward

% If the name-value pairs don't match up, throw error.
if ~all(cellfun(@ischar, (varargin(1:2:end)))) || mod(length(varargin),2)
error('Rigbox:hw:checkRewardValveCalibration:nameValueMismatch', ...
['If using input arguments, %s requires them to be constructed '...
'in name-value pairs'], mfilename);
end

% Get the `hw.DaqController` object from within the rig's hardware devices:
rig = hw.devices;
rigFields = fieldnames(rig);
% Get a cell array of each object in the `rig` struct.
rigObjs = cellfun(@(x) rig.(x), rigFields, 'UniformOutput', 0);
% Get the index of the `hw.DaqController` object in `rigObjs`
dcIdxInRig = cellfun(@(x) isa(x, 'hw.DaqController'),...
rigObjs, 'UniformOutput', 0);
try
% Get the `hw.DaqController` object from `rig` based on its fieldname.
dc = rig.(rigFields{[dcIdxInRig{:}]});
catch
error('Rigbox:hw:checkRewardValveCalibration:dcNotFound',...
['Could not find a ''hw.DaqController'' object in this rig''s \n'...
'''hardware.mat'' file.']);
end

% Get the `hw.RewardValveControl` object from within the rig's
% `hw.DaqController` `SignalGenerators`:
sgClassNames = arrayfun(@class, dc.SignalGenerators,...
'UniformOutput', false);
rvChanIdx = cellfun(@(x) strcmpi('hw.RewardValveControl', x),...
sgClassNames);
chan = dc.DaqChannelIds{rvChanIdx};

% Set default values for input args.
defaults = struct(...
'amt', 5.0, ...
'chan', chan);

% Convert user-defined `varargin` into struct.
inputs = cell2struct(varargin(2:2:end)', varargin(1:2:end)');

% Merge `inputs` and `defaults`, making sure fields for `inputs` overwrites
% the same named fields for `defaults`.
argsStruct = mergeStructs(inputs, defaults);

% De-struct input args
amt = argsStruct.amt;
chan = argsStruct.chan;

% Get the `hw.RewardValveControl` object.
rv = dc.SignalGenerators(contains(dc.DaqChannelIds, chan));
if isempty(rv)
error('Rigbox:hw:checkRewardValveCalibration:rvNotFound',...
['Could not find a `hw.RewardValveControl` object in this rig''s \n'...
'`hw.DaqController` object, or an inappropriate `chan` input \n'...
'argument was given.']);
end
%% Deliver reward

% Get the most recent, largest delivery volume, `v`, and time, `t`, taken
% to deliver that volume, from the `rv` calibrations table.
v = rv.Calibrations(end).measuredDeliveries(end).volumeMicroLitres;
t = rv.Calibrations(end).measuredDeliveries(end).durationSecs;
% Get the number of pulses required to deliver a reward of amount `amt` in
% increments of `v`.
nPulses = ceil(amt*10e2 / v);

% Set-up command, `cmd`, to send to `dc` to deliver reward.
interval = 0.2; % interval b/w pulses in s
cmd = [t; interval; nPulses]; % command to output to `dc`

% Print to screen how long it will take to check this calibration:
totalTInMins = ((interval+t)*nPulses)/60;
fprintf('This calibration check will take approximately %0.2f mins\n',...
totalTInMins);

% Change `ParamsFun` of `rv` to deliver pulses as
% [t, nPulses, frequency].
origParamsFun = rv.ParamsFun; % we'll reset `ParamsFun` to this
rv.ParamsFun = @(cmd) deal(cmd(1), cmd(3), 1/(sum(cmd(1)+cmd(2))));

% Deliver command to `dc`.
dc.command(cmd, 'foreground');

% Reset `ParamsFun`.
rv.ParamsFun = origParamsFun;

end
60 changes: 60 additions & 0 deletions tests/checkRewardValveCalibration_test.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
% preconditions:

% Get the `hw.DaqController` object from within the rig's hardware devices:
rig = hw.devices;
rigFields = fieldnames(rig);
% Get a cell array of each object in the `rig` struct.
rigObjs = cellfun(@(x) rig.(x), rigFields, 'UniformOutput', 0);
% Get the index of the `hw.DaqController` object in `rigObjs`
dcIdxInRig = cellfun(@(x) isa(x, 'hw.DaqController'),...
rigObjs, 'UniformOutput', 0);
try
% Get the `hw.DaqController` object from `rig` based on its fieldname.
dc = rig.(rigFields{[dcIdxInRig{:}]});
catch
error('Rigbox:hw:checkRewardValveCalibration:dcNotFound',...
['Could not find a ''hw.DaqController'' object in this rig''s \n'...
'''hardware.mat'' file.']);
end

% Get the `hw.RewardValveControl` object from within the rig's
% `hw.DaqController` `SignalGenerators`:
sgClassNames = arrayfun(@class, dc.SignalGenerators,...
'UniformOutput', false);
rvChanIdx = cellfun(@(x) strcmpi('hw.RewardValveControl', x),...
sgClassNames);

chan = dc.DaqChannelIds{rvChanIdx}; % NI-DAQ channel we are outputting reward to
amt = 5.0; % volume of reward (in mL)

%% Test 1: Ensure that the class of the Signal Generator output to is valid
chanIdx = contains(dc.DaqChannelIds, chan);

% Get the `hw.ControlSignalGenerator` object from which to deliver reward.
signalGen = dc.SignalGenerators(chanIdx);

assert(strcmpi(class(signalGen), 'hw.RewardValveControl'),...
['The class of the Signal Generator is not a class which can '...
'deliver reward']);

%% Test 2: Ensure that proper name-value pair arguments are used
% Ensure rig hardware is freed up:
clearvars -except amt chan
daqreset;

try
hw.checkRewardValveCalibration(amt, chan)
catch ex
errSep = strfind(ex.identifier, ':');
errName = ex.identifier(errSep(end)+1:end);
assert(strcmpi(errName, 'nameValueMismatch'));
end

try
daqreset;
hw.checkRewardValveCalibration('amt', 5.0, 'chan', 'invalidCh')
catch ex
errSep = strfind(ex.identifier, ':');
errName = ex.identifier(errSep(end)+1:end);
assert(strcmpi(errName, 'rvNotFound'));
end