Skip to content

Commit 0ee8d88

Browse files
committed
Start work on the waveform testing class
1 parent b758ba8 commit 0ee8d88

File tree

1 file changed

+266
-0
lines changed

1 file changed

+266
-0
lines changed

code/DAQmx_Scanners/waveformTester.m

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
classdef waveformTester < handle
2+
% Minimal code needed to acquire data from one channel of a 2-photon microscope
3+
%
4+
% waveformTester
5+
%
6+
%
7+
% Description:
8+
% This is a tutorial class to explore the scan waveform. The waveform for the X mirror
9+
% is played out of AO0 and sent to the X scan control card. It's also copied to AI0.
10+
% AI1 gets the galvo position feedback signal. Parameters are set by editing the properties.
11+
%
12+
%
13+
% Instructions
14+
% Start the class with the device ID of your NI acquisition board as an input argument.
15+
% Quit by closing the window showing the scanned image stream. Doing this will gracefully
16+
% stop the acquisition.
17+
% The X mirror should be on AO-0
18+
% The Y mirror should be on AO-1
19+
%
20+
% Inputs
21+
% hardwareDeviceID - [Optional, default is 'Dev1'] A string defining the device ID of your
22+
% NI acquisition board. Use the command "daq.getDevices" to find the ID
23+
% of your board. By default this is 'Dev1'
24+
% saveFname - An optional string defining the relative or absolute path of a file to which data
25+
% should be written. Data will be written as a TIFF stack. If not supplied, no data
26+
% are saved to disk.
27+
%
28+
%
29+
%
30+
% Examples
31+
% The following example shows how to list the available DAQ devices and start
32+
% waveformTester.
33+
%
34+
% >> listDeviceIDs
35+
% The devices on your system are:
36+
% aux1
37+
% aux2
38+
% Dev1
39+
% scan
40+
%
41+
% >> S=waveformTester('Dev2') % By default it's 'Dev1'
42+
% >> S.stop % stops the scanning
43+
% >> S.start % re-starts the scanning
44+
% >> S.delete % (or close the figure window)
45+
%
46+
%
47+
% % Acquire using 'Dev1' and save data to a file
48+
% >> S = waveformTester('Dev1','testImageStack.tiff')
49+
%
50+
% Requirements
51+
% DAQmx and the Vidrio dabs.ni.daqmx wrapper
52+
%
53+
% See Also:
54+
% minimalScanner
55+
56+
57+
58+
% Define properties that we will use for the acquisition. The properties are "protected" to avoid
59+
% the user changing them at the command line. Doing so would cause the acquisition to exhibit errors
60+
% because there is no mechanism for handling changes to these parameters on the fly.
61+
properties (SetAccess=private)
62+
% These properties are specific to scanning and image construction
63+
galvoAmp = 1.5 % Scanner amplitude (defined as peak-to-peak/2)
64+
pixelsPerLine = 256 % Number pixels per line
65+
waveform % The scanner waveforms will be stored here
66+
numReps=4 % How many times to repeat this waveform in one acquisiion
67+
fillFraction = 0.85 % 1-fillFraction is considered to be the turn-around time and is excluded from the image
68+
69+
70+
% Properties for the analog input end of things
71+
hAITask %The AI task handle will be kept here
72+
73+
AIChan = [0,1]
74+
AIterminalConfig = 'DAQmx_Val_PseudoDiff' %Valid values: 'DAQmx_Val_Cfg_Default', 'DAQmx_Val_RSE', 'DAQmx_Val_NRSE', 'DAQmx_Val_Diff', 'DAQmx_Val_PseudoDiff'
75+
AIrange = 0.5 % Digitise over +/- this range.
76+
77+
% Properties for the analog output end of things
78+
hAOTask % The AO task handle will be kept here
79+
AOChans = 0
80+
81+
% These properties are common to both tasks
82+
DAQDevice = 'Dev1'
83+
sampleRate = 128E3 % The sample rate at which the board runs (Hz)
84+
end % close properties block
85+
86+
87+
properties (Hidden,SetAccess=private)
88+
% These properties hold information relevant to the plot window
89+
% They are hidden as well as protected for neatness.
90+
hFig % The handle to the figure which shows the data is stored here
91+
hAxes % Handle for the image axes
92+
hPltData % Handle produced by imagesc
93+
hTitle % Handle that stores the plot title
94+
end
95+
96+
97+
98+
99+
methods
100+
101+
function obj=waveformTester
102+
% This method is the "constructor", it runs when the class is instantiated.
103+
104+
105+
% Call a method to connect to the DAQ. If the following line fails, the Tasks are
106+
% cleaned up gracefully and the object is deleted. This is all done by the method
107+
% call and by the destructor
108+
obj.connectToDAQandSetUpChannels
109+
110+
111+
fprintf('Please see "help waveformTester" for usage information\n')
112+
113+
% Build the figure window and have it shut off the acquisition when closed.
114+
obj.hFig = clf;
115+
set(obj.hFig, 'Name', 'Close figure to stop acquisition', 'CloseRequestFcn', @obj.windowCloseFcn)
116+
117+
%Make an empty axis and fill with a blank image
118+
obj.hAxes = axes('Parent', obj.hFig, 'Position', [0.05 0.05 0.9 0.9]);
119+
obj.hPltData = plot(obj.hAxes, obj.waveform);
120+
set(obj.hAxes, 'Box', 'on')
121+
122+
123+
% Start the acquisition
124+
obj.start
125+
fprintf('Close figure to quit acquisition\n')
126+
end % close constructor
127+
128+
129+
function delete(obj)
130+
% This method is the "destructor". It runs when an instance of the class is deleted.
131+
obj.hFig.delete %Closes the plot window
132+
obj.stop % Call the method that stops the DAQmx tasks
133+
134+
% The tasks should delete automatically (which causes dabs.ni.daqmx.Task.delete to
135+
% call DAQmxClearTask on each task) but for paranoia we can delete manually:
136+
obj.hAITask.delete;
137+
obj.hAOTask.delete;
138+
end %close destructor
139+
140+
141+
function connectToDAQandSetUpChannels(obj)
142+
% Note how we try to name the methods in the most descriptive way possible
143+
% Attempt to connect to the DAQ and set it up. If we fail, we close the
144+
% connection to the DAQ and tidy up
145+
try
146+
% Create separate DAQmx tasks for the AI and AO
147+
obj.hAITask = dabs.ni.daqmx.Task('signalReceiver');
148+
obj.hAOTask = dabs.ni.daqmx.Task('waveformMaker');
149+
150+
% Set up analog input and output voltage channels
151+
obj.hAITask.createAIVoltageChan(obj.DAQDevice, obj.AIChan, [], -obj.AIrange, obj.AIrange, [], [], obj.AIterminalConfig);
152+
obj.hAOTask.createAOVoltageChan(obj.DAQDevice, obj.AOChans);
153+
154+
155+
% * Set up the AI task
156+
157+
% Configure the sampling rate and the number of samples so that we are reading back
158+
% data at the end of each frame
159+
obj.generateScanWaveforms %This will populate the waveforms property
160+
161+
obj.hAITask.cfgSampClkTiming(obj.sampleRate,'DAQmx_Val_ContSamps', size(obj.waveforms,1) * 4);
162+
163+
% Call an anonymous function function to read from the AI buffer and plot the images once per frame
164+
obj.hAITask.registerEveryNSamplesEvent(@obj.readAndDisplayScanData, size(obj.waveforms,1), false, 'Scaled');
165+
166+
167+
% * Set up the AO task
168+
% Set the size of the output buffer
169+
obj.hAOTask.cfgSampClkTiming(obj.sampleRate, 'DAQmx_Val_ContSamps', size(obj.waveforms,1));
170+
171+
172+
if obj.hAOTask.sampClkRate ~= obj.hAITask.sampClkRate
173+
fprintf(['WARNING: AI task sample clock rate does not match AO task sample clock rate. Scan lines will precess.\n', ...
174+
'This issue is corrected in polishedScanner, which uses a shared sample clock between AO and AI\n'])
175+
end
176+
177+
% Allow sample regeneration (buffer is circular)
178+
obj.hAOTask.set('writeRegenMode', 'DAQmx_Val_AllowRegen');
179+
180+
% Write the waveform to the buffer with a 5 second timeout in case it fails
181+
obj.hAOTask.writeAnalogData(obj.waveforms, 5)
182+
183+
% Configure the AO task to start as soon as the AI task starts
184+
obj.hAOTask.cfgDigEdgeStartTrig(['/',obj.DAQDevice,'/ai/StartTrigger'], 'DAQmx_Val_Rising');
185+
catch ME
186+
errorDisplay(ME)
187+
%Tidy up if we fail
188+
obj.delete
189+
end
190+
end % close connectToDAQandSetUpChannels
191+
192+
193+
function start(obj)
194+
% This method starts acquisition on the AO then the AI task.
195+
% Acquisition begins immediately since there are no external triggers.
196+
try
197+
obj.hAOTask.start();
198+
obj.hAITask.start();
199+
catch ME
200+
errorDisplay(ME)
201+
%Tidy up if we fail
202+
obj.delete
203+
end
204+
end %close start
205+
206+
207+
function stop(obj)
208+
% Stop the AI and then AO tasks
209+
fprintf('Stopping the scanning AI and AO tasks\n');
210+
obj.hAITask.stop; % Calls DAQmxStopTask
211+
obj.hAOTask.stop;
212+
end %close stop
213+
214+
215+
function generateScanWaveforms(obj)
216+
% This method builds a simple ("unshaped") galvo waveform and stores it in the obj.waveform
217+
218+
% The X waveform goes from +galvoAmp to -galvoAmp over the course of one line.
219+
xWaveform = linspace(-obj.galvoAmp, obj.galvoAmp, obj.pixelsPerLine);
220+
221+
% Repeat the X waveform a few times to ease visualisation on-screen
222+
obj.waveform = repmat(xWaveform, 1, obj.numReps);
223+
224+
%Report waveform properties
225+
fprintf('Scanning with a waveform of length %d and a line period of %0.3f ms\n', ...
226+
obj.imSize, (obj.sampleRate/length(obj.waveforms))*obj.numReps*1E3 );
227+
228+
end %close generateScanWaveforms
229+
230+
231+
function readAndDisplayScanData(obj,src,evnt)
232+
% This callback method is run each time data have been acquired.
233+
% This happens because the of the listener set up in the method connectToDAQandSetUpChannels
234+
% on the "obj.hAITask.registerEveryNSamplesEvent" line.
235+
236+
% Read data off the DAQ
237+
inData = readAnalogData(src,src.everyNSamples,'Scaled');
238+
239+
obj.hPltData.YData = inData(:,1);
240+
241+
end %close readAndDisplayScanData
242+
243+
244+
245+
function windowCloseFcn(obj,~,~)
246+
% This runs when the user closes the figure window or if there is an error
247+
% Note it's also possible to run a clean-up callback function with hTask.registerDoneEvent
248+
249+
fprintf('You closed the window. Shutting down DAQ.\n')
250+
obj.delete % simply call the destructor
251+
end %close windowCloseFcn
252+
253+
end %close methods block
254+
255+
end %close the vidrio.mixed.waveformTester class definition
256+
257+
258+
259+
% Private functions not part of the class definition
260+
function errorDisplay(ME)
261+
fprintf('ERROR: %s\n',ME.message)
262+
for ii=1:length(ME.stack)
263+
fprintf(' on line %d of %s\n', ME.stack(ii).line, ME.stack(ii).name)
264+
end
265+
fprintf('\n')
266+
end

0 commit comments

Comments
 (0)