├── gui.png ├── lsmaq_icons.mat ├── prop ├── CustomCellEditor.class ├── javasource │ ├── JIDE_Grids_Developer_Guide.url │ ├── DefaultProperty.java │ └── CustomCellEditor.java ├── setCustomCellEditor.m ├── dynamicshell.m └── propertytable.m ├── config ├── .gitignore └── templatecfg.m ├── .gitignore ├── LICENSE.md ├── raw2pixeldata.m ├── chanfig.m ├── overlayfigure.m ├── MP285.m ├── defaultConfig.m ├── makeScanPattern.m ├── grabStream.m ├── matmap.m ├── lsmaq.m ├── README.md └── rigClass.m /gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danionella/lsmaq/HEAD/gui.png -------------------------------------------------------------------------------- /lsmaq_icons.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danionella/lsmaq/HEAD/lsmaq_icons.mat -------------------------------------------------------------------------------- /prop/CustomCellEditor.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danionella/lsmaq/HEAD/prop/CustomCellEditor.class -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | 5 | # But not these files... 6 | !.gitignore 7 | !templatecfg.m 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### MATLAB ### 3 | # Windows default autosave extension 4 | *.asv 5 | 6 | # OSX / *nix default autosave extension 7 | *.m~ 8 | -------------------------------------------------------------------------------- /prop/javasource/JIDE_Grids_Developer_Guide.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://www.jidesoft.com/products/JIDE_Grids_Developer_Guide.pdf 3 | -------------------------------------------------------------------------------- /prop/javasource/DefaultProperty.java: -------------------------------------------------------------------------------- 1 | import com.jidesoft.swing.JideSwingUtilities; 2 | public class DefaultProperty extends com.jidesoft.grid.Property 3 | 4 | { 5 | 6 | private Object c; 7 | 8 | public DefaultProperty(){ 9 | c = 0; 10 | } 11 | 12 | public void setValue(Object object) { 13 | Object object_0_ = c; 14 | if (!object_0_.equals(object)){ 15 | c = object; 16 | firePropertyChange("value", object_0_, c); 17 | } 18 | } 19 | 20 | public Object getValue() { 21 | return c; 22 | } 23 | 24 | public boolean hasValue() { 25 | return true; 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2018 LSMAQ team 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /raw2pixeldata.m: -------------------------------------------------------------------------------- 1 | function out = raw2pixeldata(rawdata, nSamplesPerLine, fillFraction, fillLag, nPixelsPerLine, bidir) 2 | %RAW2PIXELDATA Converts raw samples from the DAQ card into images. Called 3 | % by grabStream/samplesAcquiredFun for each image stripe. 4 | 5 | %precalc 6 | nFillSamplesPerLine = nSamplesPerLine * fillFraction; 7 | nSamplesPerPixel = (nFillSamplesPerLine / nPixelsPerLine); 8 | startFillOdd = floor(fillLag*(nSamplesPerLine - nFillSamplesPerLine -1)); 9 | startFillEven = floor(fillLag*(nSamplesPerLine - nFillSamplesPerLine -1)); 10 | 11 | %cropping data (we only need fill fraction of data) 12 | s = size(rawdata); % s(1)=number of samples in stripe; s(2)=number of channels 13 | rawdata = reshape(rawdata, [round(nSamplesPerLine), s(1)/round(nSamplesPerLine) s(2)]); 14 | 15 | if bidir 16 | temp = rawdata((1:nFillSamplesPerLine)+startFillOdd, :, :, :); 17 | temp(:, 2:2:end, :, :) = flip(rawdata((1:nFillSamplesPerLine)+startFillEven, 2:2:end, :, :), 1); 18 | rawdata = temp; 19 | else 20 | rawdata = rawdata((1:nFillSamplesPerLine)+startFillOdd, :, :, :); 21 | end 22 | 23 | %reshaping data for binning 24 | s = size(rawdata); 25 | s(end+1:4)=1; 26 | rawdata = reshape(rawdata, [round(nSamplesPerPixel) s(1)/nSamplesPerPixel s(2) s(3) s(4)]); 27 | rawdata = mean(rawdata, 1); 28 | out = shiftdim(rawdata, 1); 29 | -------------------------------------------------------------------------------- /chanfig.m: -------------------------------------------------------------------------------- 1 | function [hIm, hF] = chanfig(chan) 2 | %CHANFIG Opens channel figure for lsmaq 3 | 4 | name = ['channel ', num2str(chan)]; 5 | 6 | %replace existing figure if necessary: 7 | hF = findobj(0, 'type', 'figure', '-and', 'name', name); 8 | if isempty(hF) 9 | hF = figure('name', name, 'NumberTitle', 'off', 'Tag', 'chanfig', ... 10 | 'DoubleBuffer', 'off', 'visible', 'off'); 11 | else 12 | clf(hF) 13 | end 14 | 15 | %set figure, axis and image properties: 16 | figure(hF); 17 | pos = get(hF, 'Position'); set(hF, 'Position', [pos(1:2) 512 512]); 18 | set(hF, 'menubar', 'none', 'toolbar', 'none', 'DoubleBuffer', 'off'); 19 | set(gca, 'units', 'normalized', 'Position', [0 0 1 1]); 20 | axis square tight 21 | 22 | % hIm = imagesc(0, [0 2^16-1]/2); colormap(gray); %caxis([0 10]); 23 | hIm = imagesc(0, [0 1]); colormap(gray); %caxis([0 10]); 24 | 25 | 26 | %ca = caxis; 27 | ca = [0, 2^12]; 28 | caxis(ca) 29 | 30 | uicontrol('String', mat2str(ca), 'Fontsize', 10, 'Units', 'normalized',... 31 | 'Style','edit', 'BackgroundColor',[1 1 1], 'HorizontalAlignment', 'center',... 32 | 'Callback',@setImageMax); 33 | 34 | movegui 35 | set(hF, 'visible', 'on') 36 | 37 | function setImageMax(hObj, ~) 38 | if ~isnan(eval(get(hObj, 'String'))) 39 | caxis(eval(get(hObj, 'String'))); 40 | end -------------------------------------------------------------------------------- /overlayfigure.m: -------------------------------------------------------------------------------- 1 | function hOverlay = overlayfigure(hIm) 2 | %OVERLAYFIGURE creates an RGB overlay of existing images, which updates when source images change. 3 | % 4 | % hIm: Array of source Image handles (max 3), to be assigned to the 5 | % red, green and blue channel. 6 | % 7 | % Example: 8 | % figure, hIm(1) = imagesc(rand(100)); colormap gray 9 | % figure, hIm(2) = imagesc(rand(100)); colormap gray 10 | % overlayfigure(hIm); 11 | % hIm(1).CData = rand(100); 12 | 13 | hF = figure('name', 'Overlay', 'menubar', 'none', 'toolbar', 'none', 'DoubleBuffer', 'off', 'visible', 'off'); 14 | hF.Position(3:4) = [512, 512]; 15 | movegui 16 | 17 | hOverlay = imagesc(zeros([size(hIm(1).CData) 3])); 18 | axis square tight 19 | set(gca, 'units', 'normalized', 'Position', [0 0 1 1]); 20 | set(hF, 'visible', 'on') 21 | hListener = addlistener(hIm,'CData','PostSet',@(varargin) test_fcn(hIm, hOverlay)); 22 | hF.CloseRequestFcn = @(hObj, ev) closeRequest(hObj, hListener); 23 | test_fcn(hIm, hOverlay); 24 | 25 | function test_fcn(hIm, hOverlay) 26 | for i = 1:numel(hIm) 27 | cl = hIm(i).Parent.CLim; 28 | chan = (hIm(i).CData - cl(1)) / (cl(2)-cl(1)); 29 | chan(chan<0) = 0; 30 | chan(chan>1) = 1; 31 | hOverlay.CData(:,:,i) = chan; 32 | end 33 | 34 | 35 | function closeRequest(hObject,hListener) 36 | delete(hListener) 37 | delete(hObject) -------------------------------------------------------------------------------- /prop/javasource/CustomCellEditor.java: -------------------------------------------------------------------------------- 1 | import javax.swing.*; 2 | import javax.swing.table.*; 3 | import java.awt.*; 4 | 5 | public class CustomCellEditor extends AbstractCellEditor implements TableCellEditor { 6 | JTextField textfield = new JTextField(); 7 | JPanel panel = new JPanel(new BorderLayout()); 8 | JButton button = new JButton(); 9 | 10 | public CustomCellEditor(){ 11 | button.setPreferredSize(new Dimension(15,15)); 12 | button.setText("..."); 13 | panel.add(textfield); 14 | panel.add(button, BorderLayout.EAST); 15 | } 16 | 17 | // called when editing is completed, returns the new value to be stored in the cell. 18 | public Object getCellEditorValue() { 19 | return textfield.getText(); 20 | } 21 | 22 | // called when a cell value is edited by the user. 23 | public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int rowIndex, int vColIndex) { 24 | textfield.setText((String)value); 25 | return panel; 26 | } 27 | 28 | // this public function will allow MATLAB to change button properties 29 | public JButton getButton(){ 30 | return button; 31 | } 32 | 33 | // this public function will allow MATLAB to change textfield properties 34 | public JTextField getTextField(){ 35 | return textfield; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /prop/setCustomCellEditor.m: -------------------------------------------------------------------------------- 1 | function setCustomCellEditor(ptmh, name, fDialog) 2 | %SETCUSTOMCELLEDITOR set a custom cell editor 3 | % setCustomCellEditor(dp, fDialog) 4 | % Sets a custom cell editor for a property in inspectProps 5 | % Property table model handle object 6 | % String describing the property name 7 | % Function handle for editor dialog. This function will 8 | % be called when button is pressed. Arguments: dp, button 9 | % 10 | % Example: 11 | % [pph pth ptmh ppc] = inspectProps(prop); 12 | % setCustomCellEditor(ptmh, 'dirName', @(dp, button) dp.setValue(['''', uigetdir, '''')) 13 | % 14 | % See also inspectProps, struct2ref 15 | 16 | % Revision history: 17 | % 071101: created, BJ 18 | 19 | warning off, javaaddpath(fileparts(mfilename('fullpath'))), warning on 20 | 21 | dp = ptmh.findProperty(name); %DefaultProperty java-object 22 | 23 | if ~dp.getType.equals(java.lang.String('').getClass) 24 | error('can only set custom cell editor for Strings') 25 | end 26 | 27 | ec = com.jidesoft.grid.EditorContext(getTempName); 28 | cce = CustomCellEditor; 29 | cem = com.jidesoft.grid.CellEditorManager(); 30 | cem.initDefaultEditor; 31 | cem.registerEditor(dp.getType, cce, ec); 32 | 33 | dp.setEditorContext(ec); 34 | 35 | button = handle(cce.getButton, 'callbackProperties'); 36 | set(button, 'MouseClickedCallback', @callbackWrapper) 37 | 38 | function callbackWrapper(button, event) 39 | cce.stopCellEditing; 40 | fDialog(dp, button); 41 | ptmh.fireTableDataChanged; 42 | end 43 | 44 | function tempName = getTempName 45 | tempName = sprintf('tmp%015.0f', rand(1) * 10^15); 46 | end 47 | 48 | end -------------------------------------------------------------------------------- /prop/dynamicshell.m: -------------------------------------------------------------------------------- 1 | classdef dynamicshell < dynamicprops 2 | %DYNAMICSHELL empty object dynamically added properties 3 | % 4 | % See also ADDPROP, ADDLISTENER, DYNAMICPROPS 5 | 6 | properties 7 | 8 | end 9 | 10 | 11 | methods 12 | 13 | function obj = dynamicshell(s) 14 | if nargin >= 1 15 | for iFldn = fieldnames(s)' 16 | p = addprop(obj, iFldn{1}); 17 | p.SetObservable = true; 18 | p.GetObservable = true; 19 | p.AbortSet = true; 20 | if isstruct(s.(iFldn{1})) 21 | obj.(iFldn{1}) = dynamicshell(s.(iFldn{1})); 22 | else 23 | obj.(iFldn{1}) = s.(iFldn{1}); 24 | end 25 | end %for 26 | 27 | end %if 28 | end 29 | 30 | function addHiddenProp(obj, s) 31 | if nargin >= 2 32 | for iFldn = fieldnames(s)' 33 | p = addprop(obj, iFldn{1}); 34 | p.Hidden = true; 35 | obj.(iFldn{1}) = s.(iFldn{1}); 36 | end %for 37 | 38 | end %if 39 | end 40 | 41 | function appendStruct2Prop(obj, s) 42 | if nargin >= 2 43 | for iFldn = fieldnames(s)' 44 | p = addprop(obj, iFldn{1}); 45 | p.SetObservable = true; 46 | p.GetObservable = true; 47 | p.AbortSet = true; 48 | if isstruct(s.(iFldn{1})) 49 | obj.(iFldn{1}) = dynamicshell(s.(iFldn{1})); 50 | else 51 | obj.(iFldn{1}) = s.(iFldn{1}); 52 | end 53 | end %for 54 | end 55 | end 56 | 57 | 58 | function s = tostruct(obj) 59 | for iFldn = (fieldnames(obj))' 60 | if isa(obj.(iFldn{1}), 'dynamicshell') 61 | var = obj.(iFldn{1}).tostruct(); 62 | else 63 | var = obj.(iFldn{1}); 64 | end 65 | s.(iFldn{1}) = var; 66 | end 67 | end 68 | 69 | 70 | function fn = properties(obj) 71 | fn = sort(builtin('properties', obj)); 72 | end 73 | 74 | function fn = fieldnames(obj) 75 | fn = properties(obj); 76 | end 77 | 78 | function out = struct(obj) 79 | out = orderfields(builtin('struct', obj)); 80 | end 81 | 82 | function [pp, pt, ptm, ppc] = inspect(varargin) 83 | [pp, pt, ptm, ppc] = propertytable(varargin{:}); 84 | if nargin < 2, set(gcf, 'Name', inputname(1)), end 85 | end 86 | 87 | end %methods 88 | 89 | end %classdef 90 | -------------------------------------------------------------------------------- /config/templatecfg.m: -------------------------------------------------------------------------------- 1 | function [prop, rigprops, scanconfigs] = templatecfg % <- make sure to rename the function name to fit your file name 2 | 3 | [prop, rigprops, scanconfigs] = defaultConfig; % load defaultConfig and overwrite any parameters below 4 | 5 | % RIG COFIGURATION 6 | rigprops.AIrate = 1e6; % analog input sample rate in Hz 7 | rigprops.AOrate = 1e6/2; % analog output sample rate in Hz (has to be divisor of AIrate) 8 | rigprops.AIrange = [-1 1]; % analog input voltage range (2-element vector) 9 | rigprops.AOchans = {'/Dev1/ao0:1'}; % cell array of AO channel paths. For a single AO card, this would be a 1-element cell, e.g. {'Dev1/ao0:1'}, for two cards, this could be {'Dev1/ao0:1', 'Dev2/ao0:2'} 10 | rigprops.pmtPolarity = 1; % invert PMT polarity, if needed (value: 1 or -1) 11 | rigprops.stageCreator = @() MP285('COM3', [10 10 25]); % function that takes no arguments and returns a stage object (containing methods getPos and setPos, e.g. @() MP285('COM3', [10 10 25])) or empty 12 | rigprops.powercontrolCreator = []; % function that takes no arguments and returns a powercontrol object (containing methods getPower and setPower) 13 | rigprops.laserSyncPort = ''; % leave empty if not syncing, sets SampleClock source of AI and TimeBaseSource of AO object, pulse rate is assumed to be AIRate 14 | 15 | % DEFAULT (STARTUP) GRAB AND SCAN PROPERTIES 16 | prop.grabcfg.dirName = 'C:\DATA'; 17 | prop.grabcfg.fileBaseName = [datestr(now,'yymmdd') '_n']; 18 | prop.grabcfg.fileNumber = 1; %current file number (will be appended to name) 19 | prop.grabcfg.nFrames = 10; %number of frames to grab 20 | prop.grabcfg.stackNumXyz = [1 1 50]; %number of stacks/tiles along X/Y/Z 21 | prop.grabcfg.stackDeltaXyz = [0 0 5]; %stack and tile separation along X/Y/Z 22 | prop.grabcfg.stackSequence = 'ZXY'; %stack scan sequence e.g. 'ZXY' to scan first along Z, then X, then Y 23 | prop.grabcfg.powerDecayLength = Inf; %power decay length in um 24 | prop.scancfg.bidirectional = false; %toggle bidirectional scaning 25 | prop.scancfg.fillFraction = 800/1000; %fill fraction (fraction samples not used for flyback) 26 | prop.scancfg.sampleLag = 100; %galvo lag in AI samples 27 | prop.scancfg.nInSamplesPerLine = 2000; %input samples per line. this sets the line rate 28 | prop.scancfg.nLinesPerFrame = 400; %number of lines per frame 29 | prop.scancfg.nPixelsPerLine = 400; %number of Pixels per line 30 | prop.scancfg.scanAmp = [1 1 0 1 1]; %[X Y] amplitudes (optional: [X,Y,Z,blank,phase] amplitudes) 31 | prop.scancfg.zoom = 1; %zoom factor 32 | 33 | % ALTERNATIVE SCAN CONFIGURATIONS (can be selected from UI dropdown list) 34 | scanconfigs.default_ = prop.scancfg; 35 | scanconfigs.bi_400x400_500s = struct('bidirectional', true, 'nInSamplesPerLine', 500, ... 36 | 'fillFraction', 0.8, 'nLinesPerFrame', 400, 'nPixelsPerLine', 400, 'scanAmp', [1 1 0 1 1]); 37 | scanconfigs.bi_200x200_500s = struct('bidirectional', true, 'nInSamplesPerLine', 500, ... 38 | 'fillFraction', 0.8, 'nLinesPerFrame', 200, 'nPixelsPerLine', 200, 'scanAmp', [1 1 0 1 1]); 39 | scanconfigs.bi_200x100_500s_aniso = struct('bidirectional', true, 'nInSamplesPerLine', 500, ... 40 | 'fillFraction', 0.8, 'nLinesPerFrame', 200, 'nPixelsPerLine', 100, 'scanAmp', [1 1 0 1 1]); 41 | scanconfigs.bi_200x100_500s_iso = struct('bidirectional', true, 'nInSamplesPerLine', 500, ... 42 | 'fillFraction', 0.8, 'nLinesPerFrame', 200, 'nPixelsPerLine', 100, 'scanAmp', [1 0.5 0 1 1]); 43 | scanconfigs.uni_800x800_2ks = struct('bidirectional', true, 'nInSamplesPerLine', 2000, ... 44 | 'fillFraction', 0.8, 'nLinesPerFrame', 800, 'nPixelsPerLine', 800, 'scanAmp', [1 1 0 1 1]); 45 | -------------------------------------------------------------------------------- /MP285.m: -------------------------------------------------------------------------------- 1 | classdef MP285 < handle 2 | % MP285 - create object to interface with Sutter Instrument Micromanipulator 3 | 4 | properties 5 | uSteps_per_um % [x y z] microsteps per micrometer 6 | hPort 7 | end 8 | 9 | properties (Dependent = true) 10 | status 11 | velocity 12 | end 13 | 14 | methods 15 | %% initiation 16 | function obj = MP285(comport, uSteps_per_um) 17 | obj.hPort = instrfind('Port', comport); 18 | if ~isempty(obj.hPort) && any(strcmp(obj.hPort.Status, 'open')) 19 | error(['Port ', comport, ' busy! Type instrfind(''Port'', ''', comport,''');' ]) 20 | %try fclose(obj.hPort); delete(obj.hPort), end 21 | end 22 | obj.hPort = serial(comport); 23 | set(obj.hPort, 'BaudRate', 9600, 'Parity', 'none' , 'Terminator', {'CR', ''}, ... 24 | 'StopBits', 1, 'Timeout', 5, 'Name', 'MP285', 'ErrorFcn', @(varargin) disp('MP285 error!')); 25 | fopen(obj.hPort); 26 | if nargin < 2 27 | uSteps_per_um = [25 25 25]; 28 | warning('defaulting to 25 usteps/um') 29 | end 30 | obj.uSteps_per_um = uSteps_per_um; 31 | obj.getPos; 32 | obj.velocity = 200; 33 | end 34 | 35 | % set properties 36 | function set.velocity(obj, val) 37 | vel = ['V' typecast(bitset(uint16(val),16,0),'uint8')]; 38 | obj.write(vel); 39 | end 40 | 41 | % basic control and communication 42 | function out = flush(obj) 43 | nBytes = get(obj.hPort, 'BytesAvailable'); 44 | if nBytes > 0 45 | out = fread(obj.hPort, nBytes); 46 | else 47 | out = []; 48 | end 49 | end 50 | 51 | function status = write(obj,in) 52 | obj.flush(); 53 | fwrite(obj.hPort, [in 13]); 54 | if nargout > 0, status = fread(obj.hPort,1); end 55 | end 56 | 57 | function status = moveAbs(obj,xyz) 58 | if nargout > 0 59 | status = obj.write(['m' obj.toChar(int32(xyz.*obj.uSteps_per_um))]); 60 | else 61 | obj.write(['m' obj.toChar(int32(xyz.*obj.uSteps_per_um))]); 62 | end 63 | end 64 | 65 | function status = moveRel(obj,xyz) % in microns [x y z]' 66 | [xyzOld] = obj.getPos; 67 | % pause(0.05); 68 | if nargout > 0 69 | status = obj.moveAbs(xyz + xyzOld); 70 | else 71 | obj.moveAbs(xyz + xyzOld); 72 | end 73 | end 74 | 75 | function out = get.status(obj) % in microns 76 | obj.write('s'); 77 | out.flags = dec2binvec(fread(obj.hPort, 1, 'uint8'), 8); 78 | out.udirXYZ = fread(obj.hPort, 3, 'uint8')'; 79 | out.roe_vari = fread(obj.hPort, 1, 'uint16'); 80 | out.uoffset = fread(obj.hPort, 1, 'uint16'); 81 | out.range = fread(obj.hPort, 1, 'uint16'); 82 | out.pulse = fread(obj.hPort, 1, 'uint16'); 83 | out.uspeed = fread(obj.hPort, 1, 'uint16'); 84 | out.indevice = fread(obj.hPort, 1, 'uint8'); 85 | out.flags_2 = dec2binvec(fread(obj.hPort, 1, 'uint8'), 8); 86 | out.jumpspd = fread(obj.hPort, 1, 'uint16'); 87 | out.highspd = fread(obj.hPort, 1, 'uint16'); 88 | out.dead = fread(obj.hPort, 1, 'uint16'); 89 | out.watch_dog = fread(obj.hPort, 1, 'uint16'); 90 | out.step_div = fread(obj.hPort, 1, 'uint16'); 91 | out.step_mul = fread(obj.hPort, 1, 'uint16'); 92 | out.xspeed = fread(obj.hPort, 1, 'uint16'); 93 | out.version = fread(obj.hPort, 1, 'uint16'); 94 | obj.flush(); 95 | end 96 | 97 | function [xyz, status] = getPos(obj) % in microns 98 | obj.write('c'); 99 | xyz = fread(obj.hPort, 3, 'long')' ./ obj.uSteps_per_um; 100 | if nargout > 1, status = fread(obj.hPort, 1);end 101 | end 102 | % 103 | function delete(obj) % class destructor 104 | if ~isempty(obj.hPort), fclose(obj.hPort); delete(obj.hPort); end 105 | end 106 | 107 | % Helper functions 108 | function c = toChar(obj,in) 109 | c = typecast(in, 'uint8'); 110 | end 111 | 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /prop/propertytable.m: -------------------------------------------------------------------------------- 1 | function [pp, pt, ptm, ppc, ds] = propertytable(ds, hF) 2 | %PROPERTYTABLE lauch JIDE-based property grid 3 | % 4 | % propertytable(ds, hF) 5 | % 6 | % %ds: dynamicshell object or structure containing properties 7 | % %hF: figure handle (optional) 8 | % 9 | % See also dynamicshell 10 | 11 | % see http://undocumentedmatlab.com/blog/advanced-jide-property-grids 12 | % and http://www.jidesoft.com/products/JIDE_Grids_Developer_Guide.pdf 13 | % and http://www.mathworks.com/matlabcentral/fileexchange/38864 14 | 15 | %process input arguments 16 | if nargin < 2 || isempty(hF) 17 | hF = figure; set(hF, 'NumberTitle', 'off', 'Name', inputname(1)) 18 | set(hF, 'ToolBar', 'none', 'MenuBar', 'none') 19 | pos = get(hF, 'Position'); set(hF, 'Position', [pos(1:2) 200 pos(4)]); 20 | end 21 | 22 | %make sure props is a dynamicshell 23 | if ~isa(ds, 'dynamicshell'), ds = dynamicshell(ds); end 24 | 25 | %create java objects 26 | tablePropertyArray = javaObjectEDT(java.util.ArrayList); 27 | addChildProps(tablePropertyArray, ds); 28 | ptm = javaObjectEDT(com.jidesoft.grid.PropertyTableModel(tablePropertyArray)); 29 | pt = javaObjectEDT(com.jidesoft.grid.PropertyTable(ptm)); 30 | pp = javaObjectEDT(com.jidesoft.grid.PropertyPane(pt)); 31 | 32 | %change appearance 33 | pp.setShowDescription(false) 34 | pp.setShowToolBar(false) 35 | pp.setOrder(2); 36 | pt.expandAll; 37 | 38 | %show on screen 39 | figure(hF) 40 | [~, ppc] = javacomponent(pp); 41 | set(ppc, 'Units', 'normalized', 'Position', [0 0 1 1]) 42 | 43 | %optional: set editors and renderers 44 | cem = com.jidesoft.grid.CellEditorManager(); cem.initDefaultEditor(); 45 | cem.registerEditor(java.lang.Boolean(0).getClass, com.jidesoft.grid.BooleanCheckBoxCellEditor); 46 | crm = com.jidesoft.grid.CellRendererManager(); 47 | crm.registerRenderer(java.lang.Boolean(0).getClass, com.jidesoft.grid.BooleanCheckBoxCellRenderer) 48 | %ptmh.getProperty('zoom').setEditorContext(com.jidesoft.grid.SpinnerCellEditor.CONTEXT) 49 | 50 | % set table change callback 51 | set(ptm, 'PropertyChangeCallback', @TablePropChangeCallback); 52 | 53 | function addChildProps(parent, props) 54 | %populates the list of com.jidesoft.grid.DefaultProperty objects 55 | for iFldn = fieldnames(props)' 56 | val = props.(iFldn{1}); 57 | p = com.jidesoft.grid.DefaultProperty; 58 | p.setName(iFldn{1}); 59 | p.setType(java.lang.String('').getClass); 60 | if isstruct(val) || isa(val, 'dynamicshell') 61 | p.setEditable(false); 62 | p.setValue(''); 63 | addChildProps(p, val) %creates a sub-branch 64 | else %single property: update table and attach listener 65 | setTablePropVal(p, val) 66 | lh = event.proplistener(props, findprop(props, iFldn{1}), 'PostSet', @(dp, ev) setTablePropVal(p, ev.AffectedObject.(dp.Name))); 67 | setappdata(hF, 'proplistener', [getappdata(hF, 'proplistener') lh]); 68 | end 69 | if isa(parent, 'java.util.ArrayList') 70 | parent.add(p); 71 | elseif isa(parent, 'com.jidesoft.grid.DefaultProperty') 72 | parent.addChild(p); 73 | end 74 | end 75 | end 76 | 77 | function setTablePropVal(p, val) 78 | %called whenever a table value needs to be updated 79 | if exist('pth', 'var') && ~isempty(pth.getCellEditor), pth.getCellEditor.stopCellEditing; end 80 | if ~(isnumeric(val) || ischar(val) || islogical(val)) %not displayable 81 | p.setEditable(false); 82 | p.setValue([strrep(num2str(size(val)), ' ', 'x') class(val)]); 83 | else %single editable property 84 | p.setEditable(true); 85 | p.setValue(mat2str(val)); 86 | end 87 | end 88 | 89 | function TablePropChangeCallback(model, event) 90 | %called whenever a property on the table is changed 91 | propName = event.getPropertyName.toCharArray'; 92 | %if strcmp(propName, 'value'), return, end 93 | newValue = event.getNewValue; 94 | oldValue = event.getOldValue; 95 | prop = model.getProperty(propName); 96 | try %update dynamicshell object 97 | subs = strsplit(event.getPropertyName.toCharArray', '.'); 98 | ds = subsasgn(ds, struct('type', '.', 'subs', subs), eval(newValue)); 99 | catch %in case of error revert to old value 100 | warning(lasterr) 101 | prop.setValue(oldValue); 102 | end 103 | model.refresh; 104 | end 105 | 106 | end %propertytable 107 | 108 | -------------------------------------------------------------------------------- /defaultConfig.m: -------------------------------------------------------------------------------- 1 | function [prop, rigprops, scanconfigs] = defaultConfig 2 | 3 | % RIG COFIGURATION 4 | rigprops.AIrate = 1e6; % analog input sample rate in Hz 5 | rigprops.AOrate = 1e6/2; % analog output sample rate in Hz (has to be divisor of AIrate) 6 | rigprops.AIrange = [-1 1] * 1; % analog input voltage range (2-element vector) 7 | rigprops.AIchans = '/Dev1/ai0:1'; % path to AI channels (primary DAQ card) 8 | rigprops.shutterline = '/Dev1/PFI1'; % path to shutter output line (primary DAQ card) 9 | rigprops.AOchans = {'/Dev1/ao0:1'}; % cell array of AO channel paths. For a single AO card, this would be a 1-element cell, e.g. {'Dev1/ao0:1'}, for two cards, this could be {'Dev1/ao0:1', 'Dev2/ao0:2'} 10 | rigprops.channelOrder = {[1 2]}; % cell array of signal to channel assignments. Assign [X,Y,Z,Blank,Phase] signals (in that order, 1-based indexing) to output channels. To assign X to the first output channel, Y to the second, blank to the first of the second card and Z to the second of the second card, use {[1 2], [4 3]}. For a single output card, this could be e.g. {[1 2]} 11 | rigprops.pmtPolarity = 1; % invert PMT polarity, if needed (value: 1 or -1) 12 | rigprops.gateline = '/Dev1/port0/line0'; % path to digital output of gating/blanking signal 13 | rigprops.stageCreator = []; % function that takes no arguments and returns a stage object (containing methods getPos and setPos, e.g. @() MP285('COM3', [10 10 25])) or empty 14 | rigprops.powercontrolCreator = []; % function that takes no arguments and returns a powercontrol object (containing methods getPower and setPower) 15 | rigprops.laserSyncPort = ''; % leave empty if not syncing, sets SampleClock source of AI and TimeBaseSource of AO object, pulse rate is assumed to be AIRate 16 | 17 | % DEFAULT (STARTUP) GRAB AND SCAN PROPERTIES 18 | prop.grabcfg.dirName = 'C:\DATA'; 19 | prop.grabcfg.fileBaseName = [datestr(now,'yymmdd') '_n']; 20 | prop.grabcfg.fileNumber = 1; %current file number (will be appended to name) 21 | prop.grabcfg.nFrames = 10; %number of frames to grab 22 | prop.grabcfg.stackNumXyz = [1 1 50]; %number of stacks/tiles along X/Y/Z 23 | prop.grabcfg.stackDeltaXyz = [0 0 5]; %stack and tile separation along X/Y/Z 24 | prop.grabcfg.stackSequence = 'ZXY'; %stack scan sequence e.g. 'ZXY' to scan first along Z, then X, then Y 25 | prop.grabcfg.powerDecayLength = Inf; %power decay length in um 26 | prop.scancfg.bidirectional = false; %toggle bidirectional scaning 27 | prop.scancfg.fillFraction = 800/1000; %fill fraction (fraction samples not used for flyback) 28 | prop.scancfg.sampleLag = 100; %galvo lag in AI samples 29 | prop.scancfg.nInSamplesPerLine = 2000; %input samples per line. this sets the line rate 30 | prop.scancfg.nLinesPerFrame = 400; %number of lines per frame 31 | prop.scancfg.nPixelsPerLine = 400; %number of Pixels per line 32 | prop.scancfg.piezoStepsN = 1; %number of evenly spaced piezo steps 33 | prop.scancfg.phaseStepsN = 1; %number of evenly spaced piezo steps 34 | prop.scancfg.scanAmp = [1 1 0 1 1]; %[X Y] amplitudes (optional: [X,Y,Z,blank,phase] amplitudes) 35 | prop.scancfg.scanAngle = 0; %in-plane scan angle (optional: angles around [X,Y,Z] axes) 36 | prop.scancfg.scanOffset = [0 0 0 0 0]; %[X Y] offsets (optional: [X,Y,Z,blank,phase] offsets) 37 | prop.scancfg.zoom = 1; %zoom factor 38 | 39 | 40 | % ALTERNATIVE SCAN CONFIGURATIONS (can be selected from UI dropdown list) 41 | scanconfigs.default_ = prop.scancfg; 42 | scanconfigs.bi_400x400_500s = struct('bidirectional', true, 'nInSamplesPerLine', 500, ... 43 | 'fillFraction', 0.8, 'nLinesPerFrame', 400, 'nPixelsPerLine', 400, 'scanAmp', [1 1 0 1 1]); 44 | scanconfigs.bi_200x200_500s = struct('bidirectional', true, 'nInSamplesPerLine', 500, ... 45 | 'fillFraction', 0.8, 'nLinesPerFrame', 200, 'nPixelsPerLine', 200, 'scanAmp', [1 1 0 1 1]); 46 | scanconfigs.bi_200x100_500s_aniso = struct('bidirectional', true, 'nInSamplesPerLine', 500, ... 47 | 'fillFraction', 0.8, 'nLinesPerFrame', 200, 'nPixelsPerLine', 100, 'scanAmp', [1 1 0 1 1]); 48 | scanconfigs.bi_200x100_500s_iso = struct('bidirectional', true, 'nInSamplesPerLine', 500, ... 49 | 'fillFraction', 0.8, 'nLinesPerFrame', 200, 'nPixelsPerLine', 100, 'scanAmp', [1 0.5 0 1 1]); 50 | scanconfigs.uni_800x800_2ks = struct('bidirectional', true, 'nInSamplesPerLine', 2000, ... 51 | 'fillFraction', 0.8, 'nLinesPerFrame', 800, 'nPixelsPerLine', 800, 'scanAmp', [1 1 0 1 1]); 52 | -------------------------------------------------------------------------------- /makeScanPattern.m: -------------------------------------------------------------------------------- 1 | function out = makeScanPattern(nSamplesPerLine, fillFraction, nLinesPerFrame, scanAmpXYZBP, offsetXYZBP, angleXYZ, bidir, zoom, piezoStepsN, phaseStepsN) 2 | %MAKESCANPATTERNXY Generate mirror command signals for one frame 3 | % out = makeScanPattern(nSamplesPerLine, fillFraction, nLinesPerFrame, scanAmpXY, offsetXY, angle) 4 | % Generates an n-by-5 matrix of scan mirror command signals for one 5 | % frame using smooth, parabolic flybacks. The five columns correspond to 6 | % [X,Y,Z,blank,phase], which are X,Y,Z mirror / piezo command signals, 7 | % the Pockels cell blanking signal and a phase signal for phase stepping. 8 | % 9 | % : number of output samples per line (including flyback) 10 | % : fill fraction (fillFraction * nSamplesPerLine has to be whole-numbered) 11 | % : number of lines per frame 12 | % : 1x2 to 1x5 vector of [X,Y,Z,blank,phase] scan amplitudes 13 | % ([x,y] corresponds to [x,y,0,0,0]) 14 | % : 1x2 to 1x5 vector of [X,Y,Z,blank,phase] scan offsets 15 | % : scalar scan rotation angle in the XY plane (rotation around Z-axis) 16 | % or 1x3 vector of angles (around X-, Y- and Z-axis). In degrees 17 | % : true/false flag for bidirectional scanning 18 | % : zoom factor applied to X and Y axis before rotation and offsets 19 | % : number of slices (piezo scanning). Default 1 20 | % : number of phases scanned. Default 1 21 | % 22 | % example: 23 | % out = makeScanPattern(625, 0.8192, 32, [1 1 1], [0 0 0], [0 0 0], false, 2, 4); 24 | % figure, plot3(out(:, 1), out(:, 2), out(:, 3)), xlim([-1.1 1.1]), ylim([-1.1 1.1]) 25 | % 26 | % See also makeSawtooth, makeParabTrans 27 | % 28 | % Revision history: 29 | % 01.10.2018: added support for phase-stepping 30 | % 18.06.2015: added support for piezo z-scanning, INP 31 | % 23.07.2007: major updates for 3D scanning, BJ 32 | % 05.04.2006: created, BJ 33 | 34 | %process inputs 35 | scanAmpXYZBP(end+1:5) = 0; 36 | offsetXYZBP(end+1:5) = 0; 37 | if numel(angleXYZ) == 1, angleXYZ = [0 0 angleXYZ]; end 38 | 39 | %precalc 40 | nFillSamples = ceil(nSamplesPerLine * fillFraction); 41 | nFlybackSamples = nSamplesPerLine - nFillSamples; 42 | 43 | %make the sawtooth for the X-scanner: 44 | if bidir 45 | outX = repmat( makeBidirectional(nSamplesPerLine, fillFraction) , [nLinesPerFrame/2*piezoStepsN 1]); 46 | else 47 | outX = repmat( makeSawtooth(nFillSamples, nFlybackSamples) , [nLinesPerFrame*piezoStepsN 1]); 48 | end 49 | 50 | outY = repmat( makeSteps(nLinesPerFrame/phaseStepsN, nSamplesPerLine*phaseStepsN, nFlybackSamples) , [piezoStepsN 1]); 51 | outZ = makeSteps(piezoStepsN, nSamplesPerLine*nLinesPerFrame, nFlybackSamples); 52 | outPhi = repmat( makeSteps(phaseStepsN, nSamplesPerLine, nFlybackSamples) , [nLinesPerFrame/phaseStepsN*piezoStepsN 1]); 53 | 54 | %add blanking output (for Pockels signal) 55 | blank = [ones(nFillSamples, 1); zeros(nFlybackSamples, 1)]; 56 | blank = repmat(blank, [nLinesPerFrame*piezoStepsN 1]); 57 | 58 | %scale (apply zoom only to X and Y) 59 | out = [outX outY outZ blank outPhi] .* (scanAmpXYZBP ./ [zoom zoom 1 1 1]); 60 | 61 | %rotate 62 | angleXYZ = pi*angleXYZ/180; 63 | if any(angleXYZ ~= 0) %any(angleXYZ(1:2) ~= 0) %if doing z-scan 64 | rmZ = [cos(angleXYZ(3)) sin(angleXYZ(3)) 0; -sin(angleXYZ(3)) cos(angleXYZ(3)) 0; 0 0 1]; 65 | rmY = [cos(angleXYZ(2)) 0 sin(angleXYZ(2)); 0 1 0; -sin(angleXYZ(2)) 0 cos(angleXYZ(2))]; 66 | rmX = [1 0 0; 0 cos(angleXYZ(1)) sin(angleXYZ(1)); 0 -sin(angleXYZ(1)) cos(angleXYZ(1))]; 67 | out(:, 1:3) = (rmZ * rmY * rmX * out(:, 1:3)')'; 68 | end 69 | 70 | %add offsets 71 | out = out + offsetXYZBP; 72 | 73 | 74 | 75 | function out = makeSteps(nSteps, nSamplesPerLine, nFlybackSamples) 76 | YLevels = linspace(-1, 1, nSteps); if nSteps == 1, YLevels = 0; end 77 | nFillSamples = nSamplesPerLine - nFlybackSamples; 78 | out = repmat(YLevels, [nSamplesPerLine 1]); 79 | dV = diff([YLevels YLevels(1)]); 80 | flyback = makeParabTrans(nFlybackSamples)'; 81 | out(nFillSamples+1:end, :) = flyback.*dV+YLevels; 82 | out = out(:); 83 | 84 | function out = makeBidirectional(nSamples, fraction) 85 | 86 | f = (1-fraction)/2; 87 | x = 1:nSamples+1; 88 | x = x(1:end-1)/nSamples; 89 | indFraction = round(f*nSamples); 90 | bend = -1/(f*fraction)*(x(end-indFraction+1:end)-1).^2 + 1 + f/fraction; 91 | out = [2/fraction*(x(indFraction+1:end-indFraction))-1/fraction, bend, fliplr(bend) ]; 92 | out = [out, -out]'; 93 | 94 | function trans = makeParabTrans(nSamples) 95 | %MAKEPARABTRANS Generate a smoth parabolic transition 96 | % trans = makeParabTrans(nSamples) generates a smooth parabolic transition 97 | % (transition) for a mirror command voltage change from 0 to 1. 98 | % is the number of transition samples. 99 | % 100 | % example for a transition between -1 and 2: 101 | % level1 = -1; level2 = 2; 102 | % trans = makeParabTrans(10) 103 | % trans = trans*(level2-level1)+level1; 104 | % plot([ones(1, 10)*level1 trans ones(1, 10)*level2], '.') 105 | % 106 | % See also makeScanPatternXY, makeSawtooth 107 | % 108 | % Revision history: 109 | % 02.04.2006: created, BJ 110 | 111 | x = linspace(0, 2, nSamples + 2); 112 | trans(abs(x)<=1) = x(x<=1).^2 - 1; trans(abs(x)>1) = -(x(x>1)-2).^2 + 1; 113 | trans = (-trans(2:end-1)/trans(1) + 1)/2; 114 | 115 | 116 | function out = makeSawtooth(nFillSamples, nFlybackSamples) 117 | %MAKESAWTOOTH Generate mirror command sawtooth 118 | % out = makeSawtooth(nFillSamples, nFlybackSamples) generates a 119 | % sawtooth with a linear up-slope and smooth parabolic flyback 120 | % 121 | % See also makeScanPatternXY, makeParabTrans 122 | % 123 | % Revision history: 124 | % 08.10.2018: updated (now uses makeParabTrans), BJ 125 | % 02.04.2006: created, BJ 126 | 127 | start = (1/2)/(nFillSamples/nFlybackSamples); start = -start/(1+start); %this line computes at which x-position the slopes of both functions are equal 128 | x = linspace(start, 2-start, nFlybackSamples+2); 129 | %this implements the function y = {x^2-1 for x <=1; (x-2)^2+1 for x>1}: 130 | flyback(x<=1) = x(x<=1).^2 - 1; flyback(x>1) = -(x(x>1)-2).^2 + 1; 131 | flyback = flyback(2:end-1)/flyback(1); 132 | out = [linspace(-1, 1, nFillSamples) flyback]'; 133 | -------------------------------------------------------------------------------- /grabStream.m: -------------------------------------------------------------------------------- 1 | function [data, scannerOut] = grabStream(rig, prop, hIm, fStatus, fStripe) 2 | %GRABSTREAM Grab images and display data online 3 | % data = grabStream(rig, scancfg, grabcfg, hIm); 4 | % Scans and records images according to parameters specified 5 | % rig control structure as generated by rigStartup 6 | % scan configuration structure 7 | % grab configuration structure 8 | % Handle of an image for online data display. EraseMode 9 | % should be 'none'. 10 | % (optional) Function handle of status function 11 | % which is called by grabStream to e.g. update status 12 | % text information. Arguments: fStatus(percent, text). 13 | % (optional) Handle of a fucntion that is being called 14 | % after each stripe has been acquired and displayed on 15 | % screen. Arguments: fStripe(cdata, yIndices). 16 | % 17 | % grabStream(rig, scancfg, grabcfg, hIm, fStatus); 18 | % If the function is called without output argument it uses less memory 19 | % since data doesn't have to be stored. Can be used for "focus" mode, 20 | % when no data is acquired. 21 | % 22 | % See also raw2pixeldata, makeScanPatternXYZ 23 | % 24 | % Revision history: 25 | % 01.11.2018: moved all hard-coded DAQ functions to rigClass (and upgraded to .NET interface) 26 | % 18.06.2015: extensive modification to work with session based DAQ, added z-scanning, INP 27 | % 29.01.2009: added output of scannerOut to variable svxyz, SS 28 | % 13.11.2007: added fStripe functionality, BJ 29 | % 28.04.2006: created, BJ 30 | 31 | scancfg = prop.scancfg; 32 | grabcfg = prop.grabcfg; 33 | 34 | % process inputs and outputs 35 | isGrabbing = (nargout > 0); 36 | if nargin < 4, fStatus = @(varargin) NaN; end 37 | 38 | % Check input arguments 39 | checkCfg(); 40 | 41 | % set lines per display stripe (smallest divisor of nLinesPerFrame >= 10) 42 | nLinesPerStripe=find((rem(scancfg.nLinesPerFrame, 1:scancfg.nLinesPerFrame) == 0) & ((1:scancfg.nLinesPerFrame) >= 40), 1); 43 | 44 | % initialize 45 | iAcquiredLines = 0; 46 | outdata = zeros(scancfg.nPixelsPerLine, scancfg.nLinesPerFrame, rig.AItask.AIChannels.Count, 'int16'); 47 | data = zeros([scancfg.nPixelsPerLine scancfg.nLinesPerFrame rig.AItask.AIChannels.Count grabcfg.nFrames*scancfg.piezoStepsN], 'int16'); 48 | 49 | % AI acquisition is continuous and will be stopped after all the expected data are collected 50 | rig.setupAIlistener(@samplesAcquiredFun, nLinesPerStripe * scancfg.nInSamplesPerLine) 51 | 52 | % AO 53 | nOutSamplesPerLine = (rig.AOrate / rig.AIrate) * scancfg.nInSamplesPerLine; 54 | scannerOut = makeScanPattern(nOutSamplesPerLine, scancfg.fillFraction, scancfg.nLinesPerFrame, scancfg.scanAmp, scancfg.scanOffset, ... 55 | scancfg.scanAngle, scancfg.bidirectional, scancfg.zoom, scancfg.piezoStepsN, scancfg.phaseStepsN); 56 | scannerOut(:,1:2) = circshift(scannerOut(:,1:2), round(-(rig.AOrate / rig.AIrate) * scancfg.sampleLag)); 57 | rig.queueOutputData(scannerOut); %queueOutputData(rig.aoSession, repmat(scannerOut(:, 1:3), [grabcfg.nFrames 1])); 58 | 59 | % figure(45), plot3(scannerOut(:, 1), scannerOut(:, 2), scannerOut(:, 5)), xlim([-1.1 1.1]), ylim([-1.1 1.1]) 60 | % figure(46), plot(scannerOut) 61 | 62 | % display 63 | cax = (get(hIm, 'Parent')); if numel(cax) == 1, cax = {cax}; end 64 | set([cax{:}], 'XLim', [0 scancfg.nPixelsPerLine], 'YLim', [0 scancfg.nLinesPerFrame]) 65 | set(hIm, 'XData', [1 scancfg.nPixelsPerLine]-0.5); 66 | set(hIm, 'YData', [1 scancfg.nLinesPerFrame]-0.5); 67 | 68 | fps = rig.AIrate / (scancfg.nLinesPerFrame * scancfg.nInSamplesPerLine); 69 | 70 | %% START 71 | % restartDio(rig); 72 | t = tic; 73 | rig.start(); 74 | 75 | rig.shutterOpen() 76 | rig.hwTrigger() 77 | rig.isScanning = true; 78 | 79 | % cleanup if interrupted 80 | cleanupObj = onCleanup(@(varargin) rig.stopAndCleanup); 81 | 82 | try 83 | %WAIT until done 84 | if isGrabbing 85 | waitfor(rig, 'isScanning', false) 86 | else 87 | fStatus(NaN, ['live view @ ', num2str(fps, 3), ' fps ...']); 88 | waitfor(rig, 'isScanning', false) 89 | end 90 | catch 91 | warning(lasterr) 92 | end 93 | 94 | rig.stopAndCleanup 95 | rig.isScanning = false; 96 | % delete(rig.ailh); 97 | 98 | 99 | % NESTED FUNCTIONS 100 | function samplesAcquiredFun(raw) 101 | nCurrentFrame = floor(iAcquiredLines / scancfg.nLinesPerFrame) + 1; 102 | if ~rig.isScanning || (isGrabbing && (nCurrentFrame > grabcfg.nFrames*scancfg.piezoStepsN)) , rig.isScanning = false; pause(0), return, end 103 | 104 | %%GET RAW DATA , SHAPE INTO IMAGE 105 | %raw = event.Data; 106 | yIndices = mod(iAcquiredLines, scancfg.nLinesPerFrame) + (1:nLinesPerStripe); 107 | outdata(:, yIndices ,:, :) = raw2pixeldata(raw', scancfg.nInSamplesPerLine, scancfg.fillFraction, 0, scancfg.nPixelsPerLine, scancfg.bidirectional); 108 | iAcquiredLines = iAcquiredLines + nLinesPerStripe; 109 | 110 | if isGrabbing 111 | %%copY SINGLE FRAME BUFFER INTO LARGE MULTIFRAME OUTPUT MATRIX 112 | data(:, yIndices, :, nCurrentFrame) = outdata(:, yIndices, :); 113 | percent = (iAcquiredLines / scancfg.nLinesPerFrame) / double(grabcfg.nFrames); 114 | if toc(t) >= 0.2 %refreshing too often will overload the system 115 | fStatus(percent, ['acquiring @ ', num2str(fps, 2), ' fps ...']); 116 | t = tic; 117 | end 118 | end 119 | %% update CHANNEL FIGURE WITH LAST IMAGE 120 | for iChan = 1:length(hIm) 121 | set(hIm(iChan), 'XData', [1 scancfg.nPixelsPerLine]-0.5); 122 | set(hIm(iChan), 'YData', [1 scancfg.nLinesPerFrame]-0.5); 123 | set(hIm(iChan), 'CData', mean(outdata(:,:,iChan,:),4)'); 124 | end 125 | 126 | if nargin >=6, fStripe(outdata, yIndices); end 127 | pause(0) %makes this callback interruptable (to stop acquisition) 128 | end 129 | 130 | function checkCfg() 131 | maxGalvoFreq = 1000; 132 | minInSamplesPerLine = rig.AIrate/maxGalvoFreq/(1+scancfg.bidirectional); 133 | if scancfg.nInSamplesPerLine < minInSamplesPerLine 134 | %scancfg.nInSamplesPerLine = minInSamplesPerLine; 135 | %warning(['Number of samples cannot be smaller than ' num2str(minInSamplesPerLine) '. Set automatically to ' num2str(minInSamplesPerLine) '.']); 136 | warning(['Number of samples cannot be smaller than ' num2str(minInSamplesPerLine) ]) 137 | end 138 | %scancfg.fillFraction = floor(scancfg.fillFractionMax*scancfg.nInSamplesPerLine/scancfg.nPixelsPerLine)*scancfg.nPixelsPerLine/scancfg.nInSamplesPerLine; 139 | end 140 | 141 | end 142 | -------------------------------------------------------------------------------- /matmap.m: -------------------------------------------------------------------------------- 1 | classdef matmap < handle 2 | % MATMAP Object interface for memory maps of mat files 3 | % 4 | % OPEN: 5 | % mm = matmap(fn, dset) opens dataset dset in file fn as a memory map 6 | % mm = matmap starts an open dialog to selct a file and dataset 7 | % 8 | % CREATE: 9 | % mm = matmap(filename, dsetname, sz, dtype, chunksz) creates a file and a 10 | % memory map to that file, where fn is the file name, dset is the data set 11 | % name, sz is a vector of sizes, dtype is the data type (e.g. 'single', 12 | % 'double', 'complex single') and chunksz is the chunk size. 13 | % 14 | % EXAMPLE: 15 | % 16 | % mm = matmap('test.mat', '/data', [10 20 30 40], 'double', [10 20 1 1]); 17 | % mm(1:5,1:3,1,1) = ones(5, 3) * 50; 18 | % mm(:,:,1,1) 19 | % 20 | % See also H5CREATE, H5READ, H5WRITE, MEMMAPFILE 21 | 22 | % REVISION HISTORY: 23 | % 110701: creted, BJ 24 | % 120920: updated, BJ 25 | 26 | 27 | properties (SetAccess = private ) 28 | fn 29 | dset 30 | dtype 31 | sz 32 | end 33 | 34 | properties (Dependent = true) 35 | info 36 | end 37 | 38 | methods 39 | function obj = matmap(fn, dset, sz, dtype, chunk, comp) 40 | if nargin < 1 41 | [fn pn] = uigetfile; 42 | fn = [pn, filesep, fn]; 43 | info = h5info(fn); 44 | dsets = {info.Datasets.Name}; 45 | [s,v] = listdlg('PromptString','Select a dataset:','SelectionMode','single','ListString',dsets); 46 | dset = ['/' dsets{s}]; 47 | end 48 | if nargin > 2 49 | if nargin < 6 50 | comp = 0; 51 | end 52 | if nargin < 5 53 | chunk = sz; 54 | end 55 | if nargin < 4 56 | dtype = 'double'; 57 | end 58 | if ~exist(fn,'file') 59 | matmap_temp = 'matmap_temp'; 60 | save(fn, 'matmap_temp', '-V7.3'); 61 | fid = H5F.open(fn,'H5F_ACC_RDWR','H5P_DEFAULT'); 62 | H5L.delete(fid, '/matmap_temp', 'H5P_DEFAULT'); 63 | H5F.close(fid); 64 | end 65 | chunk(end+1:numel(sz)) = 1; 66 | if strcmp(dtype, 'complex') 67 | h5createcomplex(fn, dset, sz, chunk, comp, true); 68 | elseif strcmp(dtype, 'complex single') 69 | h5createcomplex(fn, dset, sz, chunk, comp, false); 70 | else 71 | h5create(fn, dset, sz, 'ChunkSize', chunk, 'Datatype', dtype, 'Deflate', comp); 72 | end 73 | end 74 | obj.fn = fn; 75 | obj.dset = dset; 76 | obj.dtype = obj.info.Datatype.Class; 77 | obj.sz = obj.info.Dataspace.Size; 78 | 79 | function h5createcomplex(fn, dset, sz, chunk, comp, doDouble) 80 | fid = H5F.open(fn,'H5F_ACC_RDWR','H5P_DEFAULT'); 81 | space_id = H5S.create_simple(numel(sz), fliplr(sz), fliplr(sz)); 82 | if doDouble 83 | type_id = H5T.create('H5T_COMPOUND', 16); 84 | H5T.insert( type_id, 'real', 0, 'H5T_NATIVE_DOUBLE'); 85 | H5T.insert( type_id, 'imag', 8, 'H5T_NATIVE_DOUBLE'); 86 | else 87 | type_id = H5T.create('H5T_COMPOUND', 8); 88 | H5T.insert( type_id, 'real', 0, 'H5T_NATIVE_FLOAT'); 89 | H5T.insert( type_id, 'imag', 4, 'H5T_NATIVE_FLOAT'); 90 | end 91 | dcpl = H5P.create('H5P_DATASET_CREATE'); 92 | H5P.set_chunk(dcpl,fliplr(chunk)); 93 | H5P.set_deflate(dcpl,comp); 94 | dset_id = H5D.create(fid,dset,type_id,space_id,'H5P_DEFAULT'); 95 | H5S.close(space_id); H5T.close(type_id); H5P.close(dcpl); H5D.close(dset_id); H5F.close(fid); 96 | end 97 | end 98 | 99 | function out = alldata(obj) 100 | out = h5read(obj.fn, obj.dset); 101 | if strcmp(obj.dtype, 'H5T_COMPOUND') 102 | out = out.real + 1i * out.imag; 103 | end 104 | end 105 | 106 | function var = size(obj, k) 107 | var = obj.sz; 108 | if nargin == 2 109 | var = var(k); 110 | end 111 | end 112 | 113 | function var = get.info(obj) 114 | var = h5info(obj.fn, obj.dset); 115 | end 116 | 117 | function varargout = subsref(obj, S) 118 | switch S(1).type 119 | case {'.'} 120 | [varargout{1:nargout}] = builtin('subsref',obj,S); 121 | case '()' 122 | [start, count] = obj.sub2startCount(S); 123 | varargout{1} = h5read(obj.fn, obj.dset, start, count); 124 | if strcmp(obj.dtype, 'H5T_COMPOUND') 125 | varargout{1} = varargout{1}.real + 1i * varargout{1}.imag; 126 | end 127 | end 128 | end 129 | 130 | function obj = subsasgn(obj, S, in) 131 | %if ~isreal(in), error('Doesn''t yet work for complex data!'), end 132 | [start, count] = obj.sub2startCount(S); 133 | if strcmp(obj.dtype, 'H5T_COMPOUND') 134 | h5writeComplex(obj.fn, obj.dset, in, start, count); 135 | else 136 | h5write(obj.fn, obj.dset, in, start, count); 137 | end 138 | end 139 | 140 | function ind = end(obj,k,~) 141 | sz = obj.size; 142 | ind = sz(k); 143 | end 144 | 145 | function [start, count] = sub2startCount(obj, S) 146 | sz = obj.size; 147 | dims = length(sz); 148 | start = ones(1, dims); 149 | count = start; 150 | nSubs = length(S(1).subs); 151 | 152 | for iSub = 1:length(S(1).subs) 153 | sub = S(1).subs{iSub}; 154 | if ischar(sub) && strcmp(sub, ':') 155 | start(iSub) = 1; 156 | count(iSub) = sz(iSub); 157 | elseif isnumeric(sub) 158 | if any(diff(sub) ~= 1), error('subs have to be contiguous!'), end 159 | start(iSub) = sub(1); 160 | count(iSub) = sub(end) - start(iSub) + 1; 161 | else 162 | disp(sub), error('wrong subscript provided!') 163 | end 164 | end 165 | if nSubs < dims 166 | lastSub = S(1).subs{nSubs}; 167 | if ischar(lastSub) && strcmp(lastSub, ':') 168 | count(nSubs:dims) = Inf; 169 | elseif isnumeric(lastSub) 170 | if numel(lastSub) ~= 1 171 | error(['If less than ', num2str(dims), ' dimensions are specified, the last subscript has to be scalar!']) 172 | end 173 | if lastSub > prod(sz(nSubs:end)) 174 | error('Index exceeds matrix dimensions!') 175 | end 176 | sub = zeros(1, 10); 177 | [sub(1) sub(2) sub(3) sub(4) sub(5) sub(6) sub(7) sub(8) sub(9) sub(10)] = ind2sub(sz(nSubs:end), lastSub); 178 | start(nSubs:dims) = sub(1:dims-nSubs+1); 179 | end 180 | %error(['All ' num2str(dims) ' subscript dimensions needed!']) 181 | end 182 | %start, count 183 | end 184 | 185 | end %methods 186 | 187 | end %classdef 188 | 189 | -------------------------------------------------------------------------------- /lsmaq.m: -------------------------------------------------------------------------------- 1 | function [rig, prop, hIm] = lsmaq(userConfig) 2 | %LSMAQ Starts the lsmaq UI and acquisition engine 3 | 4 | %create and configure figure 5 | if ~isempty(findobj(0, 'type', 'figure', 'tag', 'lsmaq')) 6 | error('please close existing lsmaq figures and try again') 7 | end 8 | 9 | % add prop folder to path 10 | addpath([fileparts(mfilename('fullpath')) filesep 'prop']) 11 | addpath([fileparts(mfilename('fullpath')) filesep 'config']) 12 | 13 | %get scan, grab and hardware configuration 14 | if nargin < 1 15 | userConfig = 'defaultConfig'; 16 | end 17 | [prop, rigcfg, scanconfigs] = eval(userConfig); 18 | prop = dynamicshell(prop); 19 | 20 | % Property window 21 | hF = figure; 22 | set(hF, 'NumberTitle', 'off', 'Name', 'lsmaq', 'CloseRequestFcn', @CloseRequestFcn, 'Tag', 'lsmaq',... 23 | 'ToolBar', 'none', 'MenuBar', 'none', 'resize', 'off', 'DockControls', 'off', 'Position', [0 250 250 485]) 24 | hTb = makeToolBar(hF); 25 | movegui(hF, [5+1, -5]); drawnow 26 | 27 | %property inspector (main gui) 28 | [~, pt, ptmh, ppc] = prop.inspect(hF); 29 | pth = handle(pt); 30 | set(ppc, 'units', 'pixels', 'Position', [1 1 250 485-21]) 31 | setCustomCellEditor(ptmh, 'grabcfg.dirName', @(dp, button) dp.setValue(['''', uigetdir, ''''])); 32 | 33 | %status bar 34 | statusBar = []; 35 | updateStatus(NaN, 'starting up...') 36 | 37 | rig = rigClass(rigcfg, @updateStatus); 38 | 39 | 40 | %create channel figures 41 | for i=1:double(rig.AItask.AIChannels.Count) 42 | [hIm(i), hChanF(i)] = chanfig(i); movegui([250+15+(i-1)*(512+10), -5]) 43 | end 44 | set([hF hChanF], 'WindowScrollWheelFcn', @mouseWheelCb) 45 | 46 | %initialize some shared variables to be used by sub-functions 47 | restartFocus = false; 48 | isAcquiring = false; 49 | 50 | %we are done 51 | updateStatus(0, 'ready to go!') 52 | 53 | 54 | % Creates toolbar and populates it with icons 55 | function hTb = makeToolBar(hF) 56 | hTb.Tb = uitoolbar(hF); 57 | icons = load('lsmaq_icons'); icons = icons.icons; 58 | hTb.Focus = uitoggletool(hTb.Tb, 'CData', icons.focus, 'TooltipString', 'Live View', 'onCallback', @startFocus, 'offCallback', @stopScanning); 59 | hTb.Grab = uitoggletool(hTb.Tb, 'CData', icons.grab, 'TooltipString', 'Grab', 'onCallback', @startGrab, 'offCallback', @stopScanning); 60 | hTb.Stop = uipushtool(hTb.Tb, 'CData', icons.stop, 'TooltipString', 'Stop', 'ClickedCallback', @stopScanning); 61 | hTb.Zstack = uitoggletool(hTb.Tb, 'CData', icons.stacks_blue, 'Separator', 'on', 'TooltipString', 'Acquire Stack/Tiles', 'ClickedCallback', @startZStack, 'offCallback', @stopScanning, 'enable', 'on'); 62 | 63 | hTb.ScanCfg = uisplittool(hTb.Tb, 'CData', icons.scan, 'Separator', 'on', 'TooltipString', 'Scan Configuration', 'enable', 'on'); 64 | for iFn = fieldnames(scanconfigs)' 65 | uimenu(hTb.ScanCfg,'Text',iFn{1}, 'MenuSelectedFcn', @(varargin) setprops(scanconfigs.(iFn{1}))); 66 | end 67 | function setprops(cfg) 68 | for jFn = fieldnames(cfg)' 69 | prop.scancfg.(jFn{1}) = cfg.(jFn{1}); 70 | end 71 | end 72 | end 73 | 74 | % Starts the free-running focusing 75 | function startFocus(~, ~) 76 | %warning('off', 'daq:Session:tooFrequent') 77 | stopEditing 78 | %updateStatus(NaN, 'focussing...'); 79 | set([hTb.Grab hTb.Zstack hTb.ScanCfg], 'enable', 'off') 80 | restartFocus = false; 81 | channelAspRatio(hIm, rig, prop) 82 | try 83 | grabStream(rig, prop, hIm, @updateStatus); 84 | updateStatus(0, 'ready to go!'); 85 | catch ME 86 | warning(ME.identifier, '%s', ME.message) 87 | stopScanning() 88 | updateStatus(0, ME.message); 89 | end 90 | set([hTb.Grab hTb.Focus hTb.Zstack], 'enable', 'on', 'state', 'off'); 91 | set([hTb.ScanCfg], 'enable', 'on') 92 | if restartFocus, restartFocus = false; pause(0.1), set(hTb.Focus, 'state', 'on'); end 93 | 94 | pth.Enabled = true; 95 | end 96 | 97 | % Starts grabbing 98 | function startGrab(~, ~) 99 | fn = sprintf('%s%s%s%04.0f.mat', prop.grabcfg.dirName, filesep, prop.grabcfg.fileBaseName, prop.grabcfg.fileNumber); 100 | if exist(fn, 'file'), warndlg('file exists'), return, end 101 | stopEditing 102 | isAcquiring = true; 103 | set([hTb.Focus hTb.Zstack hTb.ScanCfg], 'enable', 'off') 104 | channelAspRatio(hIm, rig, prop); 105 | try 106 | tic 107 | data = grabStream(rig, prop, hIm, @updateStatus); 108 | toc 109 | config = prop.tostruct; 110 | save(fn, 'data', 'config', '-v7.3'); 111 | fprintf('Saved to file %s \n', fn); 112 | updateStatus(0, 'ready to go!') 113 | catch ME 114 | warning(ME.identifier, '%s', ME.message) 115 | stopScanning() 116 | updateStatus(0, ME.message) 117 | end 118 | set([hTb.Grab hTb.Focus hTb.Zstack], 'enable', 'on', 'state', 'off'); 119 | set([hTb.ScanCfg], 'enable', 'on') 120 | prop.grabcfg.fileNumber = prop.grabcfg.fileNumber + 1; 121 | pth.Enabled = true; 122 | end 123 | 124 | function startZStack(~, ~) 125 | fn = sprintf('%s%s%s%04.0f.mat', prop.grabcfg.dirName, filesep, prop.grabcfg.fileBaseName, prop.grabcfg.fileNumber); 126 | if exist(fn, 'file'), warndlg('file exists'), return, end 127 | stopEditing 128 | set(hTb.Focus, 'enable', 'off') 129 | updateStatus(NaN, 'Acquiring stacks...') 130 | coords = getCoords(prop.grabcfg.stackNumXyz, prop.grabcfg.stackDeltaXyz, prop.grabcfg.stackSequence); 131 | nSlices = prod(prop.grabcfg.stackNumXyz); 132 | startPos = rig.stage.getPos; 133 | if ~isempty(rig.powercontrol) && ~isinf(prop.grabcfg.powerDecayLength) 134 | startPower = rig.powercontrol.getPower; 135 | end 136 | for iSlice = 1:nSlices 137 | if ~strcmp(get(hTb.Zstack, 'state'), 'on'), continue, end 138 | updateStatus(iSlice/nSlices, sprintf('Acquiring slice %d of %d (%s)', iSlice, nSlices, mat2str(coords(iSlice, :))) ) 139 | if ~isempty(rig.powercontrol) && ~isinf(prop.grabcfg.powerDecayLength) && (coords(iSlice, 3) ~= 0) 140 | rig.powercontrol.setPower(startPower * exp(-coords(iSlice, 3)/prop.grabcfg.powerDecayLength)); 141 | pause(0.25) 142 | end 143 | rig.stage.moveAbs(coords(iSlice, :) + startPos); 144 | pause(0.25); 145 | data = grabStream(rig, prop, hIm, @updateStatus); 146 | if iSlice == 1 147 | sz = size(data); sz(end+1:4) = 1; sz(5) = nSlices; 148 | mm = matmap(fn, '/data', sz, 'int16', sz(1:2)); 149 | end 150 | mm(:,:,:,:,iSlice) = data; 151 | end 152 | rig.stage.moveAbs(startPos) 153 | if ~isempty(rig.powercontrol) && ~isinf(prop.grabcfg.powerDecayLength) 154 | rig.powercontrol.setPower(startPower) 155 | end 156 | config = prop.tostruct; 157 | save(fn, 'config', '-append') 158 | fprintf('Saved to file %s \n', fn); 159 | set([hTb.Grab hTb.Focus hTb.Zstack ], 'enable', 'on', 'state', 'off'); 160 | set([hTb.ScanCfg], 'enable', 'on'); 161 | prop.grabcfg.fileNumber = prop.grabcfg.fileNumber + 1; 162 | updateStatus(0, 'ready to go!') 163 | pth.Enabled = true; 164 | 165 | function coords = getCoords(nXYZ, dXYZ, stackSequence) 166 | order = (stackSequence=='X') + 2*(stackSequence=='Y') + 3*(stackSequence=='Z'); %turn sttring into numeric stack order 167 | [nd(:,:,:,1), nd(:,:,:,2), nd(:,:,:,3)] = ndgrid((0:nXYZ(1)-1)*dXYZ(1), (0:nXYZ(2)-1)*dXYZ(2), (0:nXYZ(3)-1)*dXYZ(3)); 168 | nd = permute(nd, [order 4]); 169 | coords = reshape(nd, [], 3); 170 | end 171 | end 172 | 173 | function stopScanning(~, ~) 174 | rig.shutterClose(); 175 | rig.isScanning = false; 176 | %uiresume(hChanF(1)) 177 | end 178 | 179 | function CloseRequestFcn(hF, ~) 180 | try 181 | rig.stopAndCleanup() 182 | catch ME 183 | warning(ME.identifier, '%s', ME.message) 184 | end 185 | delete(hF) 186 | delete(hChanF) 187 | delete(rig.stage) 188 | %CHECK IF THIS IS REALLY NECCESSARY: 189 | daqreset 190 | end 191 | 192 | function mouseWheelCb(~, event) 193 | %changes zoom 194 | if isAcquiring, return, end 195 | scrollCount = -event.VerticalScrollCount; 196 | steps = 3; 197 | newzoom = 2^(round(scrollCount + log(prop.scancfg.zoom)/log(2^(1/steps)))/steps); 198 | newzoom = max([newzoom 0.125]); 199 | prop.scancfg.zoom = eval(mat2str(newzoom, 3)); 200 | restartFocus = true; 201 | stopScanning(); 202 | %uiresume(hChanF(1)) 203 | end 204 | 205 | function stopEditing 206 | if ~isempty (pth.getCellEditor) 207 | pth.getCellEditor.stopCellEditing 208 | end 209 | pth.Enabled = false; 210 | drawnow 211 | end 212 | 213 | function updateStatus(percent, text) 214 | % I think this part sometimes causes errors 215 | if isempty(statusBar) 216 | [statusBar, ~] = javacomponent(javax.swing.JProgressBar, [1 1+485-20 250 20]); 217 | statusBar.StringPainted = true; 218 | statusBar.BorderPainted = false; 219 | end 220 | if isnan(percent) 221 | set(statusBar, 'Indeterminate', true) 222 | else 223 | set(statusBar, 'Indeterminate', false) 224 | set(statusBar, 'Value', percent * 100) 225 | end 226 | if nargin == 2 227 | set(statusBar, 'String', text) 228 | end 229 | drawnow 230 | end 231 | 232 | function channelAspRatio(hIm, rig, prop) 233 | for j = 1:rig.AItask.AIChannels.Count 234 | try 235 | hIm(j).Parent.DataAspectRatio = [prop.scancfg.nPixelsPerLine/prop.scancfg.nLinesPerFrame*abs(prop.scancfg.scanAmp(2)/prop.scancfg.scanAmp(1)) 1 1]; 236 | catch 237 | end 238 | end 239 | end 240 | end 241 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LSMAQ 2 | LSMAQ is a lightweight and flexible laser scanning microscope acquisition software written in MATLAB. It supports National Instruments hardware for galvo-based scanning. Present capabilities include: 3 | - pulse synchronization with MHz-rate lasers (as in 3P microscopy) 4 | - tiled volume acquisition 5 | - arbitrary plane 3D scanning 6 | - phase-stepping for wavefront shaping / [deep imaging](https://doi.org/10.1038/nphoton.2016.252) 7 | 8 | ## Should I use it? 9 | Most likely not. If you are an end user of two-photon microscopes, you will probably be well served by existing software packages like [ScanImage](http://scanimage.vidriotechnologies.com). The ScanImage team does a very good job at keeping their software up to date with new developments that most paying end users are interested in. 10 | 11 | To minimize code and maximize flexibility, LSMAQ exposes all scanning parameters, performs few checks on user input and generally assumes that the user knows the hardware limitations of their setup. The LSMAQ team cannot offer support to end users. 12 | 13 | LSMAQ was created by microscope developers aiming for increased flexibility and ease of customisation. Scripting and quick code modification are facilitated by a clear separation between scanning engine and UI frontend, as well as a lightweight and minimal code base. Adding a new custom property to the UI takes a single line. If you are looking for flexibility or free software for three-photon microscopy, give LSMAQ a try. 14 | 15 | ## Requirements 16 | - MATLAB (tested with version 2018b, earlier versions since 2009a likely supported). The DAQ toolbox is *not* required as LSMAQ directly calls NI-DAQmx functions through MATLAB's .NET interface. 17 | - National Instruments data acquisition hardware: at least one high-speed (MHz) multifunction NI DAQ board. Tested with NI USB-6356 (primary) and NI USB-6343 (secondary) 18 | 19 | ## Installation 20 | 1. Install NI-DAQmx, making sure to include .NET support (tested with version 18.5, earlier versions likely supported) 21 | 2. Clone this repository or copy the contents of this folder to your hard drive. 22 | 3. Review defaultConfig.m to confirm that is reflects your DAQ hardware and wiring (see below) 23 | 24 | ## Wiring 25 | LSMAQ will flexibly support your custom DAQ card wiring as long as that wiring is configured in defaultConfig.m or your custom configuration file. To make your life easier and minimise the edits needed, we suggest using the following default hardware wiring: 26 | 27 | #### Primary DAQ card (e.g. NI USB-6356): 28 | - PMT inputs: AI ports 0 and 1 (up to 8 input channels supported) 29 | - Galvo outputs: AO ports 0 and 1 30 | - Connect PFI12 to USER1 using the screw terminals (USER1 will be the trigger output port) 31 | - Connect USER1 to PFI0 (trigger input) 32 | - Connect PFI1 to the shutter 33 | - Optional: wire P0.0 to USER2 and use as laser blanking signal (to laser or Pockels cell) 34 | - Optional: connect the laser's pulse sync signal to PFI5 (tested with 1 MHz pulse rate). Adjust sample timing with the cable length of PMT signal and laser sync cables (~5 ns/m coax cable) 35 | 36 | #### Optional: Secondary DAQ card for additional AO channels (e.g. NI USB-6343): 37 | - Additional outputs (e.g. to Pockels cell or piezo): AO ports 0, 1, etc 38 | - Connect PFI7 on the primary DAQ card to PFI7 on all other cards (AO clock sync) 39 | - Connect USER1 on the primary DAQ card to PFI0 on all cards (trigger signal) 40 | 41 | ## Configure / customize 42 | 43 | #### defaultConfig.m 44 | This file defines the startup scan and grab properties, as well as your hardware configuration. Open this file and review the *rig configuration* section to make sure the settings reflect your hardware and wiring. You can also modify the grab and scan configuration in this file or start LSMAQ (section below) to adjust the properties in the UI. 45 | 46 | ## Properties 47 | 48 | | Grab properties | Description | 49 | | --- | --- | 50 | | `dirName` | Save path. Image data and scan configuration are saved as .mat files in HDF5 format. Files can be opened in MATLAB and any other software that supports HDF5 (including FIJI/ImageJ with the HDF5 Plugin) 51 | | `fileBaseName` | Each file name will start with this string 52 | | `fileNumber` | The next file name will end with this number 53 | | `nFrames` | Grab this many frames (per slice/tile) 54 | | `stackNumXyz` | 3-element vector. When acquiring slices or tiles, acquire [x y z] slices/tiles along x, y, and z 55 | | `stackDeltaXyz` | 3-element vector. Slice/tile separation along [x y z]. Example: to take a stack of 50 slices separated by 5 µm along Z, set stackNumXyz to `[1 1 50]` and stackDeltaXyz to `[0 0 5]`. To acquire a tiled volume of two tiles along X, 3 along Y and 50 slices along Z, with an X/Y tile separation of 200 µm, set stackNumXyz to `[2 3 50]` and stackDeltaXyz to `[200 200 5]`. 56 | | `stackSequence` | 3-character string. Aquire slices and tiles in this order. Example: to first move along Z, then X then Y, set this to 'ZXY' (default). 57 | | `powerDecayLength` | The power decay length constant in µm. When taking z-stacks, the laser power will be adjusted according to p = p0⋅e(-z/powerDecayLength). To disable power adjustment, set to `Inf` (default). 58 | 59 | | Scan properties | Description | 60 | | --- | --- | 61 | | `bidirectional` | Toggle bi-directional scanning. `true` or `false` 62 | | `fillFraction` | Fraction of the line not used for flyback. Recommendation for a standard 1.25 MHz analog input rate: 0.8192 (this is 1024/1250; having 1024 fill samples per 1 ms line makes binning by orders of 2 convenient) 63 | | `sampleLag` | Lag of the true position versus command position in analog input samples. 64 | | `nInSamplesPerLine` | Number of sample per line (including flyback). This setting sets the line rate. At 1.25 MHz, 1250 samples per line would result in a line rate of 1 kHz and 2500 samples in 0.5 kHz. 65 | | `nLinesPerFrame` | Number of horizontal lines (vertical image size in pixels) 66 | | `nPixelsPerLine` | Number of pixels per horizontal line (horizontal image size). This setting determines sample binning. `nPixelsPerLine` has to be a divisor of `nInSamplesPerLine * fillFraction` 67 | | `piezoStepsN` | Number of slices that a piezo-based objective scanner is scanning along Z. Default value (without scanner): 1 68 | | `phaseStepsN` | Number of phases scanned by a phase-stepper. Phase stepping is performed for each line. When stepping 4 phase steps, the Y mirror will only advance every 4 lines, so that each line is scanned 4 times. `phaseStepsN` has to be a divisor of `nLinesPerFrame`. Default value (no phase stepping): 1 69 | | `scanAmp` | 2-element vector with X and Y scan amplitudes. Output voltages will be multiplied with this value. The default value, `[1 1]`, will result in a -1V to +1V voltage command signal to both galvos at zoom 1. Directions can be flipped by scaling with negative values. Example: `[1 -1]` will flip the Y-axis. Optional: 5-element vector with scan amplitudes for X, Y, Z-piezo, Pockels-cell, phase-stepper. 70 | | `scanOffset` | 2-element vector with X and Y scan offsets. Output voltages will be offset by this value (after scaling). Default: `[0 0]`. Optional: 5-element vector with scan offsets for X, Y, Z-piezo, Pockels-cell, phase-stepper. 71 | | `scanAngle` | 3-element vector of rotation angles (in degrees) along X, Y, or Z axis. Example: for a 30 deg rotation in the XY plane, set this property to `[0 0 30]`. With the help of an objective piezo, this can be used for arbitrary plane rotation in 3D, but make sure that the hardware supports the new fast axis direction (or slow down scanning until it does. Use sparingly and keep in mind that only one mirror is designed to be the fast one. Default: `[0 0 0]` 72 | | `zoom` | Image zoom. Higher values result in smaller image sizes. Keep in mind that scanning speed may have to be lowered for lowest zoom levels (highest scan amplitudes). The zoom can also be set by using the mouse scroll wheel in the UI. Example: If `scanAmp = [1 1]` and `zoom = 2`, the output voltage range to the X galvo will be -0.5V to +0.5V (the output voltage range to the Y galvo depends on the image size in pixels, as the pixel aspect ratio is kept at 1 when both scanAmp values are the same). 73 | 74 | ## Quick start 75 | Type `lsmaq` to run. This should open a property window and one or more channel windows. 76 | 77 | ![gui screenshot](gui.png) 78 | 79 | Use the property window to edit the configuration. Press enter when done editing or click outside the text field. Start scanning by pressing the play button. Adjust the zoom with the mouse scroll wheel. Stop by pressing play again or by pressing the stop button. Grab with the record button. Record stacks or tiled volumes with the blue button. Close the LSMAQ UI by closing the property window (press 'X'). 80 | 81 | To control LSMAQ from the command line, use this alternative way to start lsmaq: `[rig, prop, hIm] = lsmaq;`. This will still open a UI as above, but it will also allow you to do the following: 82 | - You can adjust scan and grab properties (`prop.scancfg` and `prop.grabcfg`) using your own code. For example, type `prop.scancfg.zoom = 4` in the matlab prompt. This will immediately adjust the UI. The link is bidirectional. If you change a property in the UI, it will be updated in `prop`. 83 | - You can start scanning using command-line. Type `data = grabStream(rig, prop, hIm);` to grab data using the configuration in `prop` (this will display data while it is acquired. To run entirely without display, omit the third parameter and type `data = grabStream(rig, prop);`) 84 | 85 | To start LSMAQ with custom hardware and/or scan configuration, create a copy of `config/templatecfg.m`, rename it (e.g. `yourcfg.m`) and modify it according to your needs. Then provide the configuration file name (without extension) as an argument: `[rig, prop, hIm] = lsmaq('yourcfg')` 86 | 87 | #### Example 1: acquire 5 images at zoom levels 1 to 5 88 | Start lsmaq with `[rig, prop, hIm] = lsmaq;`. You can use the UI to find a sample area of interest. Stop scanning, then execute this code to loop through zoom levels. 89 | 90 | ```matlab 91 | prop.grabcfg.nFrames = 1; 92 | for i = 1:5 93 | prop.scancfg.zoom = i; 94 | data(:,:,:,:,i) = grabStream(rig, prop, hIm); 95 | end 96 | ``` 97 | 98 | ## Under the hood 99 | 100 | LSMAQ consists of a command-line scan engine with a UI on top. 101 | - Scan engine 102 | - `grabStream` is the main scan function. Its takes hardware information (initialized by `rigClass`) plus scan/grab configuration (e.g. from `defaultProps`) and outputs image data. It talks to `makeScanPattern` to create analog output signals and `raw2pixeldata` to process raw analog input into image frames. 103 | - UI 104 | - `lsmaq` starts the UI, consisting of channel figures created by `chanfig` and a property table of a `dynamicshell` object. Stacks and tiled volumes are saved with the help of `matmap`, a convenient way to preallocate large data sets on the disk and save them in compressed HDF5 format. 105 | - `dynamicshell` is a class to turn a MATLAB structure into an object that can be passed by reference. Its members can be previewed in a simple property table, forming the main part of the LSMAQ property window. 106 | 107 | The following examples will illustrate the purpose of these functions, and how the UI sits on top of the command-line code. 108 | 109 | #### Example 2: command-line acquisition without any graphics 110 | ```matlab 111 | %load default scan and grab configuration as a structure 112 | [prop, rigcfg] = defaultConfig; % or [prop, rigcfg] = yourcfg 113 | %initialize your hardware 114 | rig = rigClass(rigcfg); 115 | %change configuration as needed, for example: 116 | prop.grabcfg.nFrames = 10; 117 | prop.scancfg.nLinesPerFrame = 400; 118 | prop.scancfg.nPixelsPerLine = 400; 119 | %acquire: 120 | data = grabStream(rig, prop); 121 | ``` 122 | 123 | #### Example 3: same as above, but with a simple UI for changing properties 124 | Here we will turn the `prop` structure into a `dynamicshell` object. This object will still look and feel a lot like a MATLAB structure (you can read and write its fields in the usual way), with two important differences: First, you can pass the data by reference. Second, there is a simple UI to inspect its fields graphically. 125 | 126 | ```matlab 127 | %load default scan and grab configuration as a structure 128 | [prop, rigcfg] = defaultConfig; % or [prop, rigcfg] = yourcfg 129 | %initialize your hardware 130 | rig = rigClass(rigcfg); 131 | %convert the structure into a `dynamicshell` object. 132 | prop = dynamicshell(prop) 133 | %inspect the object in a property table 134 | prop.inspect 135 | %add two channel figures 136 | hIm(1) = chanfig(1) 137 | hIm(2) = chanfig(2) 138 | %when done, acquire: 139 | data = grabStream(rig, prop, hIm); 140 | ``` 141 | 142 | This is, more or less, what `lsmaq` does – plus colored buttons. 143 | 144 | ## Wish list 145 | - Stage support: right now only the Sutter MP285 stage is supported. Support for new stages should be easy to add once the need arises. 146 | - Resonant scanning. Two possible avenues would be to build on top of Vidriotech's code for NI FPGA-based acquisition or interface fast DAQ cards, e.g. from Alazartech (see [Scanbox](https://scanbox.org/) or [ScanImage B](https://ptrrupprecht.wordpress.com/2016/12/01/matlab-code-for-instrument-control-of-a-resonant-scanning-microscope/)) 147 | 148 | ## Contributors 149 | Benjamin Judkewitz, Ioannis Papadopoulos, Spencer Smith, Maximilian Hoffmann, Diego Di Battista, Malinda Tantirigama 150 | 151 | ## See also 152 | 153 | * [ScanImage](https://vidriotechnologies.com/scanimage/) 154 | * [SimpleMScanner](https://github.com/tenss/SimpleMScanner/) 155 | * [PTRRupprecht/Instrument-Control](https://github.com/PTRRupprecht/Instrument-Control) 156 | * [ACQ4](https://github.com/acq4/acq4) 157 | * [SciScan](https://sciscan.scientifica.uk.com/) 158 | * [HelioScan](http://helioscan.github.io/HelioScan/) 159 | -------------------------------------------------------------------------------- /rigClass.m: -------------------------------------------------------------------------------- 1 | classdef rigClass < dynamicprops 2 | %rigClass holds all the variables linked with the setup hardware (Analog I/O channels, triggers etc.) 3 | 4 | properties (SetAccess=private) %check these settings. If you are not sure about your device names, check NI MAX Automation explorer 5 | AIrate % analog input sample rate in Hz 6 | AOrate % analog output sample rate in Hz (has to be divisor of AIrate) 7 | AIrange % analog input voltage range (2-element vector) 8 | AIchans % path to AI channels (primary DAQ card) 9 | shutterline % path to shutter output line (primary DAQ card) 10 | AOchans % cell array of AO channel paths. For a single AO card, this would be a 1-element cell, e.g. {'Dev1/ao0:1'}, for two cards, this could be {'Dev1/ao0:1', 'Dev2/ao0:2'} 11 | channelOrder % cell array of signal to channel assignments. Assign [X,Y,Z,Blank,Phase] signals (in that order, 1-based indexing) to output channels. To assign X to the first output channel, Y to the second, blank to the first of the second card and Z to the second of the second card, use {[1 2], [4 3]}. For a single output card, this could be e.g. {[1 2]} 12 | pmtPolarity % invert PMT polarity, if needed (value: 1 or -1) 13 | gateline % path to digital output of gating/blanking signal 14 | stageCreator % function that takes no arguments and returns a stage object (containing methods getPos and setPos, e.g. @() MP285('COM3', [10 10 25])) or empty 15 | powercontrolCreator % function that takes no arguments and returns a powercontrol object (containing methods getPos and setPos) or an empty scalar ('@() []') 16 | end 17 | 18 | properties 19 | laserSyncPort % leave empty if not syncing, sets SampleClock source of AI and TimeBaseSource of AO object, pulse rate is assumed to be AIRate 20 | stage 21 | powercontrol 22 | isScanning = false; 23 | end 24 | 25 | properties (SetAccess=private) 26 | AItask 27 | AIreader 28 | AIlistener 29 | AOtask 30 | AOwriter 31 | ParkTask 32 | ParkWriter 33 | TriggerTask 34 | ShutterTask 35 | GateTask 36 | GateTaskWriter 37 | GateCloseTask 38 | GateCloseWriter 39 | ShutterWriter 40 | end 41 | 42 | methods 43 | 44 | %rigClass constructor 45 | function obj = rigClass(rigcfg, fStatus) 46 | if nargin >=1 47 | for iFn = fieldnames(rigcfg)' 48 | obj.(iFn{1}) = rigcfg.(iFn{1}); 49 | end 50 | end 51 | if nargin < 2 || isempty(fStatus) 52 | fprintf(1, 'Starting up rig: '); 53 | fStatus = @(fraction, text) fprintf(1, '\b\b\b%02.0f%%', fraction*100); 54 | end 55 | 56 | % load NIDAQmx .NET assembly 57 | fStatus(0/6, 'starting up: loading DAQmx...') 58 | try 59 | NET.addAssembly('NationalInstruments.DAQmx'); 60 | import NationalInstruments.DAQmx.* 61 | catch 62 | error('Error loading .NET assembly! Check NIDAQmx .NET installation.') 63 | end 64 | 65 | % Reset DAQ boards 66 | fStatus(1/6, 'starting up: resetting DAQ...') 67 | for iDev = unique(strtok([{obj.AIchans} obj.AOchans], '/')) 68 | DaqSystem.Local.LoadDevice(iDev{1}).Reset 69 | end 70 | 71 | % Setting up device objects 72 | fStatus(2/6, 'starting up: setting up DAQ...') 73 | obj.AItask = NationalInstruments.DAQmx.Task; 74 | obj.AItask.AIChannels.CreateVoltageChannel(obj.AIchans, '', AITerminalConfiguration.Differential,obj.AIrange(1),obj.AIrange(2), AIVoltageUnits.Volts); 75 | obj.AItask.Timing.ConfigureSampleClock(obj.laserSyncPort, obj.AIrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100) 76 | %obj.AItask.Timing.ConfigureSampleClock('', obj.AIrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100) 77 | %obj.AItask.Timing.SampleClockTimebaseSource = 'PFI5'; 78 | %obj.AItask.Timing.SampleClockTimebaseRate = 4e6; 79 | obj.AItask.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger('PFI0', DigitalEdgeStartTriggerEdge.Rising); %obj.AItask.ExportSignals.ExportHardwareSignal(ExportSignal.StartTrigger, 'PFI0'); 80 | obj.AItask.Control(TaskAction.Verify); 81 | obj.AIreader = AnalogUnscaledReader(obj.AItask.Stream); 82 | 83 | 84 | obj.AOtask{1} = NationalInstruments.DAQmx.Task; 85 | obj.AOtask{1}.AOChannels.CreateVoltageChannel(obj.AOchans{1}, '',-10, 10, AOVoltageUnits.Volts); 86 | obj.AOtask{1}.Stream.WriteRegenerationMode = WriteRegenerationMode.AllowRegeneration; 87 | obj.AOtask{1}.Timing.ConfigureSampleClock('', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100) 88 | if ~isempty(obj.laserSyncPort) 89 | obj.AOtask{1}.Timing.SampleClockTimebaseSource = obj.laserSyncPort; 90 | obj.AOtask{1}.Timing.SampleClockTimebaseRate = obj.AIrate; 91 | else 92 | obj.AOtask{1}.Timing.SampleClockTimebaseSource = '100MHzTimebase'; 93 | obj.AOtask{1}.Timing.SampleClockTimebaseRate = 100e6; 94 | end 95 | obj.AOtask{1}.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger('PFI0', DigitalEdgeStartTriggerEdge.Rising); 96 | obj.AOtask{1}.ExportSignals.ExportHardwareSignal(ExportSignal.SampleClock, 'PFI7'); 97 | obj.AOtask{1}.Control(TaskAction.Verify); 98 | obj.AOwriter{1} = AnalogMultiChannelWriter(obj.AOtask{1}.Stream); 99 | 100 | for i = 2:numel(obj.AOchans) 101 | obj.AOtask{i} = NationalInstruments.DAQmx.Task; 102 | obj.AOtask{i}.AOChannels.CreateVoltageChannel(obj.AOchans{2}, '',-10, 10, AOVoltageUnits.Volts); 103 | obj.AOtask{i}.Stream.WriteRegenerationMode = WriteRegenerationMode.AllowRegeneration; 104 | obj.AOtask{i}.Timing.ConfigureSampleClock('PFI7', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100) 105 | obj.AOtask{i}.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger('PFI0', DigitalEdgeStartTriggerEdge.Rising); 106 | obj.AOtask{i}.Control(TaskAction.Verify); 107 | obj.AOwriter{i} = AnalogMultiChannelWriter(obj.AOtask{i}.Stream); 108 | end 109 | 110 | for i = 1:numel(obj.AOchans) 111 | obj.ParkTask{i} = NationalInstruments.DAQmx.Task; 112 | obj.ParkTask{i}.AOChannels.CreateVoltageChannel(obj.AOchans{i}, '',-10, 10, AOVoltageUnits.Volts); 113 | obj.ParkTask{i}.Control(TaskAction.Verify); 114 | obj.ParkWriter{i} = AnalogMultiChannelWriter(obj.ParkTask{i}.Stream); 115 | end 116 | 117 | obj.GateTask = NationalInstruments.DAQmx.Task; 118 | obj.GateTask.DOChannels.CreateChannel(obj.gateline,'',ChannelLineGrouping.OneChannelForEachLine); 119 | obj.GateTask.Stream.WriteRegenerationMode = WriteRegenerationMode.AllowRegeneration; 120 | obj.GateTask.Timing.ConfigureSampleClock('', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100) 121 | obj.GateTask.Timing.SampleClockTimebaseSource = obj.AOtask{1}.Timing.SampleClockTimebaseSource; 122 | obj.GateTask.Timing.SampleClockTimebaseRate = obj.AOtask{1}.Timing.SampleClockTimebaseRate; 123 | obj.GateTask.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger('PFI0', DigitalEdgeStartTriggerEdge.Rising); 124 | obj.GateTask.Control(TaskAction.Verify); 125 | obj.GateTaskWriter = DigitalSingleChannelWriter(obj.GateTask.Stream); 126 | 127 | obj.GateCloseTask = NationalInstruments.DAQmx.Task; 128 | obj.GateCloseTask.DOChannels.CreateChannel(obj.gateline,'',ChannelLineGrouping.OneChannelForEachLine); 129 | obj.GateCloseTask.Control(TaskAction.Verify); 130 | obj.GateCloseWriter = DigitalSingleChannelWriter(obj.GateCloseTask.Stream); 131 | 132 | obj.ShutterTask = NationalInstruments.DAQmx.Task; 133 | obj.ShutterTask.DOChannels.CreateChannel(obj.shutterline,'',ChannelLineGrouping.OneChannelForEachLine); 134 | obj.ShutterTask.Control(TaskAction.Verify); 135 | obj.ShutterWriter = DigitalSingleChannelWriter(obj.ShutterTask.Stream); 136 | 137 | obj.TriggerTask = NationalInstruments.DAQmx.Task; 138 | primaryDev = strtok(obj.AIchans, '/'); 139 | obj.TriggerTask.COChannels.CreatePulseChannelTime(['/' primaryDev '/Ctr0'], '', COPulseTimeUnits.Seconds, COPulseIdleState.Low, 0, 0.1, 0.1); 140 | obj.TriggerTask.Control(TaskAction.Verify); 141 | 142 | fStatus(4/6, 'starting up: adding stage...'); 143 | if ~isempty(obj.stageCreator) 144 | obj.stage = obj.stageCreator(); 145 | end 146 | 147 | fStatus(5/6, 'starting up: adding power control...'); 148 | if ~isempty(obj.powercontrolCreator) 149 | obj.powercontrol = obj.powercontrolCreator(); 150 | end 151 | 152 | fStatus(1); fprintf(1, '\n'); 153 | end 154 | 155 | function shutterClose(obj) 156 | % Closes shutter 157 | obj.ShutterWriter.WriteSingleSampleSingleLine(true, false); 158 | end 159 | 160 | function shutterOpen(obj) 161 | % Opens shutter 162 | obj.ShutterWriter.WriteSingleSampleSingleLine(true, true); 163 | end 164 | 165 | function hwTrigger(obj) 166 | % Launch hardware trigger 167 | obj.TriggerTask.Start 168 | obj.TriggerTask.WaitUntilDone(-1) 169 | obj.TriggerTask.Stop 170 | end 171 | 172 | function park(obj) 173 | for iWriter = 1:numel(obj.ParkWriter) 174 | obj.channelOrder{iWriter} 175 | obj.ParkWriter{iWriter}.WriteMultiSample(true, zeros(numel(obj.channelOrder{iWriter}), 2)); 176 | end 177 | end 178 | 179 | function queueOutputData(obj, scannerOut) 180 | % called to load data to the output cards 181 | import NationalInstruments.DAQmx.* 182 | nsamples = size(scannerOut, 1); 183 | obj.AOtask{1}.Timing.ConfigureSampleClock('', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, nsamples) 184 | if ~isempty(obj.laserSyncPort) 185 | obj.AOtask{1}.Timing.SampleClockTimebaseSource = obj.laserSyncPort; 186 | obj.AOtask{1}.Timing.SampleClockTimebaseRate = obj.AIrate; 187 | obj.GateTask.Timing.SampleClockTimebaseSource = obj.laserSyncPort; 188 | obj.GateTask.Timing.SampleClockTimebaseRate = obj.AIrate; 189 | else 190 | obj.AOtask{1}.Timing.SampleClockTimebaseSource = '100MHzTimebase'; 191 | obj.AOtask{1}.Timing.SampleClockTimebaseRate = 100e6; 192 | obj.GateTask.Timing.SampleClockTimebaseSource = '100MHzTimebase'; 193 | obj.GateTask.Timing.SampleClockTimebaseRate = 100e6; 194 | end 195 | obj.AOwriter{1}.WriteMultiSample(false, scannerOut(:, obj.channelOrder{1})'); 196 | obj.GateTaskWriter.WriteMultiSamplePort(false, uint32(scannerOut(:, 4)'>0)); 197 | for iWriter = 2:numel(obj.AOwriter) 198 | obj.AOtask{iWriter}.Timing.ConfigureSampleClock('PFI7', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, nsamples) 199 | obj.AOwriter{iWriter}.WriteMultiSample(false, scannerOut(:, obj.channelOrder{iWriter})'); 200 | end 201 | end 202 | 203 | function setupAIlistener(obj, fun, nsamples) 204 | % sets up a samples acquired listener (execute every samples) 205 | import NationalInstruments.DAQmx.* 206 | buffersize = max([nsamples*2 1000000]); 207 | buffersize = ceil(buffersize/nsamples)*nsamples; %to make sure buffer size is an integer multiple of nsamples 208 | obj.AItask.Timing.ConfigureSampleClock(obj.laserSyncPort,obj.AIrate,SampleClockActiveEdge.Rising,SampleQuantityMode.ContinuousSamples,buffersize); 209 | obj.AItask.EveryNSamplesReadEventInterval = nsamples; 210 | obj.AIlistener = addlistener(obj.AItask, 'EveryNSamplesRead', @(~, ev) fun(obj.pmtPolarity.*obj.AIreader.ReadInt16(nsamples).int16)); 211 | end 212 | 213 | function start(obj) 214 | % start AI and AO 215 | for iTask = [obj.AOtask {obj.AItask} {obj.GateTask}] 216 | iTask{1}.Start 217 | end 218 | end 219 | 220 | function stopAndCleanup(obj, varargin) 221 | % stop everything and cleanup appropriately 222 | obj.shutterClose; 223 | delete(obj.AIlistener) 224 | 225 | for iTask = [{obj.AItask} obj.AOtask {obj.GateTask}] 226 | iTask{1}.Stop 227 | iTask{1}.Control(NationalInstruments.DAQmx.TaskAction.Unreserve) 228 | end 229 | 230 | obj.GateCloseWriter.WriteSingleSampleSingleLine(true, false); 231 | obj.park(); 232 | 233 | obj.isScanning = false; 234 | end 235 | 236 | end %methods 237 | 238 | end %classdef 239 | --------------------------------------------------------------------------------