Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added option to display calibration plot within expServer #245

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 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
189 changes: 144 additions & 45 deletions +srv/expServer.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ function expServer(useTimelineOverride, bgColour)
% harware file is used.
%
% Key bindings:
% q - Quit expServer.
% h - View list of key bindings.
% t - Toggle Timeline on and off. The default state is defined in the
% hardware file but may be overridden as the first input argument.
% w - Toggle reward on and off. This switches the output of the first
Expand All @@ -22,9 +24,12 @@ function expServer(useTimelineOverride, bgColour)
% file.
% space - Deliver default reward, specified by the DefaultCommand
% property in the hardware file.
% m - Perform water calibration.
% m - Perform reward calibration for the current reward controller.
% k - View the current water calibration for current reward controller.
% b - Toggle the background colour between the default and white.
% g - Perform gamma correction
% 1 - Select first reward controller.
% 2 - Select second reward controller.
%
%
% See also MC, io.WSJCommunicator, hw.devices, srv.prepareExp, hw.Timeline
Expand All @@ -35,13 +40,19 @@ function expServer(useTimelineOverride, bgColour)

%% Parameters
global AGL GL GLU %#ok<NUSED>
quitKey = KbName('q');
rewardToggleKey = KbName('w');
rewardPulseKey = KbName('space');
rewardCalibrationKey = KbName('m');
gammaCalibrationKey = KbName('g');
timelineToggleKey = KbName('t');
toggleBackground = KbName('b');
key = struct(...
'quit', KbName('q'), ...
'help', KbName('h'), ...
'rewardToggle', KbName('w'), ...
'rewardPulse', KbName('space'), ...
'rewardCalibration', KbName('m'), ...
'viewCalibration', KbName('k'), ...
'gammaCalibration', KbName('g'), ...
'timelineToggle', KbName('t'), ...
'toggleBackground', KbName('b'), ...
'selectFirst', KbName('1'), ...
'selectSecond', KbName('2'));

rewardId = 1;
% Function for constructing a full ID for warnings and errors
fullID = @(id) strjoin([{'Rigbox:srv:expServer'}, ensureCell(id)],':');
Expand Down Expand Up @@ -105,11 +116,8 @@ function expServer(useTimelineOverride, bgColour)
rig.stimWindow.BackgroundColour = bgColour;
rig.stimWindow.open();

fprintf('\n<q> quit, <w> toggle reward, <t> toggle timeline\n');
fprintf(['<%s> reward pulse, <%s> perform reward calibration\n' ...
'<%s> perform gamma calibration\n'], KbName(rewardPulseKey), ...
KbName(rewardCalibrationKey), KbName(gammaCalibrationKey));
log('Started presentation server on port %i', communicator.DefaultListenPort);
fprintf('\n<%s> quit, <%s> help\n', key.quit, key.help);
log('Started presentation server on port %i', communicator.DefaultListenPort)

if nargin < 1 || isempty(useTimelineOverride)
% toggle use of timeline according to rig default setting
Expand All @@ -119,6 +127,7 @@ function expServer(useTimelineOverride, bgColour)
end

running = true;
calibrationDisplayed = false;

%% Main loop for service
while running
Expand All @@ -131,19 +140,24 @@ function expServer(useTimelineOverride, bgColour)
[~, firstPress] = KbQueueCheck;

% check if the quit key was pressed
if firstPress(quitKey) > 0
log('Quitting (quit key pressed)');
if firstPress(key.quit) > 0
log('Quitting (quit key pressed)')
running = false;
end

% check if help key was pressed
if firstPress(key.help) > 0
showHelp();
end

% check if the quit key was pressed
if firstPress(timelineToggleKey) > 0
if firstPress(key.timelineToggle) > 0
toggleTimeline();
end

% check for reward toggle
if firstPress(rewardToggleKey) > 0
log('Toggling reward valve');
if firstPress(key.rewardToggle) > 0
log('Toggling reward valve')
curr = rig.daqController.Value(rewardId);
sig = rig.daqController.SignalGenerators(rewardId);
if curr == sig.OpenValue
Expand All @@ -154,36 +168,42 @@ function expServer(useTimelineOverride, bgColour)
end

% check for reward pulse
if firstPress(rewardPulseKey) > 0
if firstPress(key.rewardPulse) > 0
log('Delivering default reward');
def = [rig.daqController.SignalGenerators(rewardId).DefaultCommand];
rig.daqController.command(def);
end

% check for reward calibration
if firstPress(rewardCalibrationKey) > 0
log('Performing a reward delivery calibration');
if firstPress(key.rewardCalibration) > 0
log('Performing a reward delivery calibration')
calibrateWaterDelivery();
end

% check for gamma calibration
if firstPress(gammaCalibrationKey) > 0
log('Performing a gamma calibration');
calibrateGamma();
% check for view calibration
if firstPress(key.viewCalibration) > 0
calibrationDisplayed = ~calibrationDisplayed;
iff(calibrationDisplayed, @() viewWaterCalibration, @() rig.stimWindow.flip)
end

if firstPress(toggleBackground) > 0
log('Changing background to white');
whiteScreen();
% check for gamma calibration
if firstPress(key.gammaCalibration) > 0
log('Performing a gamma calibration')
calibrateGamma();
end

if firstPress(KbName('1')) > 0
rewardId = 1;
end
if firstPress(KbName('2')) > 0
rewardId = 2;
if firstPress(key.toggleBackground) > 0
log('Changing background to white')
% WHITESCREEN Changes screen background to white
rig.stimWindow.BackgroundColour = rig.stimWindow.White;
rig.stimWindow.flip();
rig.stimWindow.BackgroundColour = bgColour;
end

% Toggle reward ID
if firstPress(key.selectFirst) > 0, rewardId = 1; end
if firstPress(key.selectSecond) > 0, rewardId = 2; end

% pause a little while to allow other OS processing
pause(5e-3);
end
Expand Down Expand Up @@ -310,7 +330,7 @@ function handleMessage(id, data, host)
fid = fopen(hwInfo, 'w');
fprintf(fid, '%s', obj2json(rig));
fclose(fid);
if ~strcmp(dat.parseExpRef(expRef), 'default') && ~isempty(getOr(dat.paths, 'databaseURL'))
if ~strcmp(dat.parseExpRef(expRef), 'default') && ~isempty(getOr(rig.paths, 'databaseURL'))
try
alyx.registerFile(hwInfo);
catch ex
Expand All @@ -329,27 +349,106 @@ function handleMessage(id, data, host)
end

function calibrateWaterDelivery()
% CALIBRATEWATERDELIVERY Performs measured reward deliveries for calibration
% Runs a water calibration for the currently selected reward ID,
% saves to the hardware file and displays a plot of the calibration.
%
% See also viewWaterCalibration, hw.calibrate
assert(isfield(rig, 'scale'), fullID('noScaleObject'), ...
'reward calibrations require a scale object in the hardware file')
daqController = rig.daqController;
chan = daqController.ChannelNames(rewardId);
%perform measured deliveries

% Perform measured deliveries
rig.scale.init();
calibration = hw.calibrate(chan, daqController, rig.scale, 20e-3, 150e-3);
rig.scale.cleanup();
%different delivery durations appear in each column, repeats in each row
%from the data, make a measuredDelivery structure
ul = [calibration.volumeMicroLitres];
log('Delivered volumes ranged from %.1ful to %.1ful', min(ul), max(ul));

rigHwFile = fullfile(pick(dat.paths, 'rigConfig'), 'hardware.mat');
% Different delivery durations appear in each column, repeats in each
% row from the data, make a measuredDelivery structure
ul = [calibration.volumeMicroLitres];
log('Delivered volumes ranged from %.1ful to %.1ful', min(ul), max(ul))

% Save the calibration into the rig hardware file
rigHwFile = fullfile(rig.paths.rigConfig, 'hardware.mat');
save(rigHwFile, 'daqController', '-append');

% Show a plot of the new calibration
viewWaterCalibration()
calibrationDisplayed = true;
end

function whiteScreen()
% WHITESCREEN Changes screen background to white
rig.stimWindow.BackgroundColour = rig.stimWindow.White;
function viewWaterCalibration()
% VIEWWATERCALIBRATION Displays a plot of the most recent reward calibration
% Prints a plot of valve open time vs reward volume from the last
% saved calibration of the currently selected reward controller.
%
% See also calibrateWaterDelivery

% Check if calibration is available for current reward Signal Generator
sigGens = rig.daqController.SignalGenerators;
noCalibration = ...
rewardId > length(sigGens) || ...
~isprop(sigGens(rewardId), 'Calibrations') || ...
isempty(sigGens(rewardId).Calibrations);

if noCalibration
% If empty, log missing calibration and return.
log('No reward calibration found for reward controller')
return
end

% Fetch the most recent calibration
log('Displaying calibration')
[newestDate, I] = max([sigGens(rewardId).Calibrations.dateTime]);
c = sigGens(rewardId).Calibrations(I);

% Create a figure and plot the data
fig = figure('Color', 'w', 'Visible', 'off');
plot([c.measuredDeliveries.durationSecs], ...
[c.measuredDeliveries.volumeMicroLitres], 'x-');

% Set some labels, etc.
xlabel('Duration (sec)');
ylabel('Volume (\muL)');
set(gca, 'FontSize', 16, 'YLim', [0 5])
title(datestr(newestDate))

% Draw the plot to the screen
cdata = getOr(getframe(fig), 'cdata'); % Get the cdata from the plot
imageDisplay = rig.stimWindow.makeTexture(cdata); % Load texture
rig.stimWindow.BackgroundColour = rig.stimWindow.White; % Match background
rig.stimWindow.drawTexture(imageDisplay); % Draw plot texture
rig.stimWindow.flip; % Show on screen
rig.stimWindow.BackgroundColour = bgColour; % Restore background colour
close(fig) % Close our invisible figure
end

function showHelp()
% VIEWHELP Print the hotkeys for expServer
% Displays the list of keys and their functions to the log and to the
% Stimulus Window.

% Convert key codes to key names
keyNames = mapToCell(@KbName, struct2cell(key));
msg = sprintf(['Displaying help\n\r',...
'<%s> quit \n', ...
'<%s> help \n', ...
'<%s> toggle reward \n', ...
'<%s> reward pulse \n', ...
'<%s> perform reward calibration \n', ...
'<%s> view reward calibration \n', ...
'<%s> perform gamma calibration \n', ...
'<%s> toggle timeline \n', ...
'<%s> toggle white screen \n', ...
'<%s> select 1st reward controller \n', ...
'<%s> select 2nd reward controller'], keyNames{:});

% Draw white text to centre of screen at 40 chars per line, 1px spacing
w = rig.stimWindow.White;
rig.stimWindow.drawText(msg, 'centerblock', 'center', w, 1, 40);
rig.stimWindow.flip();
rig.stimWindow.BackgroundColour = bgColour;
log(msg) % Display in command window too
end

function calibrateGamma()
Expand Down Expand Up @@ -393,7 +492,7 @@ function saveGamma(cal)
% SAVEGAMMA Save calibration struct to saved stimWindow object
% Loads saved stimWindow object from this rig's hardware file, updates
% the Calibration property with input, then saves.
rigHwFile = fullfile(pick(dat.paths, 'rigConfig'), 'hardware.mat');
rigHwFile = fullfile(rig.paths.rigConfig, 'hardware.mat');
stimWindow = load(rigHwFile,'stimWindow');
stimWindow = stimWindow.stimWindow;
stimWindow.Calibration = cal;
Expand Down
2 changes: 2 additions & 0 deletions tests/checkCoverage.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
% Try to divine test function location
funLoc = fileparts(which(strrep(testFile,'_test','')));
end
assert(~isempty(funLoc), ...
'Failed to find file/package under test, please provide the location')
import matlab.unittest.TestRunner
import matlab.unittest.plugins.CodeCoveragePlugin
runner = TestRunner.withTextOutput;
Expand Down
Loading