diff --git a/Modules/+Drivers/APTMotor.m b/Modules/+Drivers/APTMotor.m index 8709e19a5..f8602e806 100644 --- a/Modules/+Drivers/APTMotor.m +++ b/Modules/+Drivers/APTMotor.m @@ -31,7 +31,8 @@ APTSystem = Drivers.APTSystem.instance; devices = APTSystem.getDevices; devices = num2cell(double(devices.USB_STEPPER_DRIVE)); - devices = [{'0'},cellfun(@num2str,devices,'uniformoutput',false)]; + devices = cellfun(@num2str,devices,'uniformoutput',false); + delete(APTSystem) % Needs to be deleted to update properly delete(f); end % Use this to create/retrieve instance associated with serialNum diff --git a/Modules/+Experiments/@PolarisationSpectrum/PolarisationSpectrum.m b/Modules/+Experiments/@PolarisationSpectrum/PolarisationSpectrum.m index d3e2b3398..8ee157b4c 100644 --- a/Modules/+Experiments/@PolarisationSpectrum/PolarisationSpectrum.m +++ b/Modules/+Experiments/@PolarisationSpectrum/PolarisationSpectrum.m @@ -14,9 +14,9 @@ % diamondbase - diamondbase data - properties(SetObservable,AbortSet) + properties(SetObservable,GetObservable,AbortSet) angles = '0:10:180'; % string of rotations (in degrees) at which spectra will be measured. Can specify a list or MATLAB range - motor_serial_number = @Drivers.APTMotor.getAvailMotors; % Serial number for the rotation mount, to be used to create a driver for the rotation mount. Must be connected through APT Config first. + motor_serial_number = Prefs.MultipleChoice('help_text','Serial number of APT motor controlling the HWP','set','set_motor_serial_number','allow_empty',true) spec_experiment = Experiments.Spectrum.instance % Handle for spectrum experiment to be run. Settings for the experiment accessed from GUI motor_move_time = 30; % Maximum time allowed for motor to move between positions motor_home_time = 120; % Maximum time allowed for the motor to home itself @@ -47,6 +47,11 @@ % Constructor (should not be accessible to command line!) obj.spec_experiment = Experiments.Spectrum.instance; obj.loadPrefs; % Load prefs specified as obj.prefs + + % Find available motor serial numbers + mp = obj.get_meta_pref('motor_serial_number'); + mp.choices = Drivers.APTMotor.getAvailMotors(); % set new choices + obj.set_meta_pref('motor_serial_number', mp); end end @@ -67,7 +72,7 @@ function abort(obj) dat.meta = obj.meta; end - function set.motor_serial_number(obj,val) + function val = set_motor_serial_number(obj,val,~) val_as_double = str2double(val); % must be double to instantiate motor assert(~isnan(val_as_double),'Motor SN must be a valid number.') diff --git a/Modules/+Experiments/@Saturation/Saturation.m b/Modules/+Experiments/@Saturation/Saturation.m index d859fde5e..501874a0f 100644 --- a/Modules/+Experiments/@Saturation/Saturation.m +++ b/Modules/+Experiments/@Saturation/Saturation.m @@ -1,105 +1,94 @@ classdef Saturation < Modules.Experiment - % Continuously acquires data from the APD and Thorlabs PM100 power meter and plots them. - % User should rotate the polarizer/HWP. - + % Saturation changes intensity on a sample using a HWP (motorised or manually moved) and monitors the APD to measure saturation. Optionally also monitors a power meter to calibrate measurement with power. + + properties(SetObservable,GetObservable) + angles = Prefs.String('0','help_text', 'Matlab expression evaluated to find angles at which to measure APDs','units','degree','set','setAngle','allow_empty',false); + exposure = Prefs.Double(100, 'help_text', 'Exposure time to measure APD counts','units','ms','min',0,'allow_nan',false) + motor_move_time = Prefs.Double(30, 'help_text', 'Maximum time allowed for the motor to move','units','s','min',0,'allow_nan',false) + motor_home_time = Prefs.Double(120, 'help_text', 'Maximum time allowed for the motor to home','units','s','min',0,'allow_nan',false) + motor_serial_number = Prefs.MultipleChoice('help_text','Serial number of APT motor controlling the HWP','set','set_motor_serial_number','allow_empty',true) + APD_line = Prefs.String('APD1','help_text','NiDAQ line to apd','allow_empty',false); + APD_sync_line = Prefs.String('CounterSync','help_text','NiDAQ synchronisation line','allow_empty',false); + + end properties - pm_data; - apd_data; - linein = 'APD1'; - lineout = 'CounterSync'; - acquire = false; % this tracks when the user wants to stop acquiring data - nsamples = 1; % number of samples the APD collects - wavelength = 532; - prefs = {'linein', 'lineout', 'acquire', 'nsamples', 'wavelength'}; + prefs = {'angles','exposure','motor_move_time','motor_home_time','motor_serial_number'}; % String representation of desired prefs + % show_prefs = {}; % Use for ordering and/or selecting which prefs to show in GUI + %readonly_prefs = {}; % CC will leave these as disabled in GUI (if in prefs/show_prefs) + end + properties(SetAccess=private,Hidden) + % Internal properties that should not be accessible by command line + % Advanced users should feel free to alter these properties (keep in mind methods: abort, GetData) + data = [] % Useful for saving data from run method + meta = [] % Useful to store meta data in run method + abort_request = false; % Flag that will be set to true upon abort. Use in run method! + angle_list + rot %Handle for rotation mount driver end - properties (SetAccess=private) - PM100; - counter; + methods(Static) + % Static instance method is how to call this experiment + % This is a separate file + obj = instance() end - methods(Access=private) function obj = Saturation() - obj.loadPrefs; - obj.PM100 = Drivers.PM100.instance(); - obj.counter = Drivers.Counter.instance(obj.linein,obj.lineout); - end - end - - methods(Static) - function obj = instance() - mlock; - persistent Object - if isempty(Object) || ~isvalid(Object) - Object = Experiments.Saturation(); - end - obj = Object; + % Constructor (should not be accessible to command line!) + obj.loadPrefs; % Load prefs specified as obj.prefs + + % Find available motor serial numbers + mp = obj.get_meta_pref('motor_serial_number'); + mp.choices = Drivers.APTMotor.getAvailMotors(); % set new choices + obj.set_meta_pref('motor_serial_number', mp); end end methods - run(obj,status,managers,ax) - - function delete(obj) - obj.PM100.delete; - end + run(obj,status,managers,ax) % Main run method in separate file function abort(obj) - obj.acquire = false; + % Callback for when user presses abort in CC + obj.abort_request = true; end function dat = GetData(obj,stageManager,imagingManager) - % Saves the in v. out power, the excitation wavelength, and the dwell time and number of samples for the APD - dat.in_power = obj.pm_data; - dat.out_power = obj.apd_data; - - dat.in_wavelength = obj.PM100.get_wavelength(); - dat.dwell_time = obj.counter.dwell; - dat.nsamples = obj.nsamples; + % Callback for saving methods + dat.data = obj.data; + dat.meta = obj.meta; end - function settings(obj,panelH,~,~) - % Creates a button for the user to stop acquiring data once it has started and a place for user to set the - % measurement wavelength of the PM - spacing = 2.25; + % Set methods allow validating property/pref set values + function newVal = setAngle(obj,val,pref) + obj.angle_list = str2num(val); + newVal = val; + end - uicontrol(panelH,'style','text','string','Excitation Wavelength (nm):','horizontalalignment','right',... - 'units','characters','position',[0 spacing*3 25 1.25]); + function val = set_motor_serial_number(obj,val,~) + if isempty(val) + % If '', delete handle and short-circuit + delete(obj.rot); % Either motor obj or empty + obj.rot = []; + return + end - uicontrol(panelH,'style','edit','string',obj.wavelength,... - 'units','characters','callback',@obj.set_wl,... - 'horizontalalignment','left','position',[26 spacing*3 20 1.5]); - - uicontrol(panelH,'style','text','string','APD Dwell Time:','horizontalalignment','right',... - 'units','characters','position',[0 spacing*2 25 1.25]); + val_as_double = str2double(val); % must be double to instantiate motor - uicontrol(panelH,'style','edit','string',obj.counter.dwell,... - 'units','characters','callback',@obj.set_dwell,... - 'horizontalalignment','left','position',[26 spacing*2 20 1.5]); - - uicontrol(panelH,'style','text','string','APD Number of Samples:','horizontalalignment','right',... - 'units','characters','position',[0 spacing 25 1.25]); - - uicontrol(panelH,'style','edit','string',obj.nsamples,... - 'units','characters','callback',@obj.set_nsample,... - 'horizontalalignment','left','position',[26 spacing 20 1.5]); - end - - function stop_acquire(obj, varargin) - % Callback function for the stop acquisition button - obj.acquire = false; - end - - function set_wl(obj, src, varargin) - obj.wavelength = str2num(get(src, 'string')); - end + assert(~isnan(val_as_double),'Motor SN must be a valid number.') + if isnan(val_as_double) + return + end + % Handle proper deleting of smotor driver object + delete(obj.rot); % Either motor obj or empty + obj.rot = []; - function set_dwell(obj, src, varargin) - obj.counter.dwell = str2num(get(src, 'string')); - end + if val_as_double == 0 + %Leave obj.rot empty if no serial number selected + return % Short circuit + end - function set_nsample(obj, src, varargin) - obj.nsamples = str2num(get(src, 'string')); + % Add new motor + obj.rot = Drivers.APTMotor.instance(val_as_double, [0 360]); end + end -end \ No newline at end of file +end diff --git a/Modules/+Experiments/@Saturation/instance.m b/Modules/+Experiments/@Saturation/instance.m new file mode 100644 index 000000000..c83e1d7a8 --- /dev/null +++ b/Modules/+Experiments/@Saturation/instance.m @@ -0,0 +1,20 @@ +function obj = instance(varargin) + % This file is what locks the instance in memory such that singleton + % can perform properly. + % For the most part, varargin will be empty, but if you know what you + % are doing, you can modify/use the input (just be aware of singleton_id) + mlock; + persistent Objects + if isempty(Objects) + Objects = Experiments.Saturation.empty(1,0); + end + for i = 1:length(Objects) + if isvalid(Objects(i)) && isequal(varargin,Objects(i).singleton_id) + obj = Objects(i); + return + end + end + obj = Experiments.Saturation(varargin{:}); + obj.singleton_id = varargin; + Objects(end+1) = obj; +end \ No newline at end of file diff --git a/Modules/+Experiments/@Saturation/run.m b/Modules/+Experiments/@Saturation/run.m index f4247b509..350279fd0 100644 --- a/Modules/+Experiments/@Saturation/run.m +++ b/Modules/+Experiments/@Saturation/run.m @@ -1,39 +1,77 @@ -function run(obj,statusH,managers,ax) -statusWin = statusH.Parent.Parent; -button = findall(statusWin,'tag','AbortButton'); -newButton = add_button(button,'Stop Acquire'); -newButton.Callback = @obj.stop_acquire; -set(statusH,'string','Starting data acquisition...'); -drawnow; +function run( obj,status,managers,ax ) + % Main run method (callback for CC run button) + obj.abort_request = false; + status.String = 'Experiment started'; + ctr = Drivers.Counter.instance(obj.APD_line, obj.APD_sync_line); % Instantiate APD counter + drawnow; -panel = ax.Parent; -delete(ax) -ax(1) = subplot(1,2,1,'parent',panel); -ax(2) = subplot(1,2,2,'parent',panel); + % Edit this to include meta data for this experimental run (saved in obj.GetData) + obj.meta.prefs = obj.prefs2struct; + obj.meta.position = managers.Stages.position; % Save current stage position (x,y,z); + obj.meta.angles = obj.angle_list; %Angles corresponding to each spectrum -% Flip the acquire boolean to true, set PM wavelength, and initialize data structures -obj.acquire = true; -obj.PM100.set_wavelength(obj.wavelength); -obj.pm_data = []; -obj.apd_data = []; + % Check that rot is not empty and valid + assert(~isempty(obj.rot) && isvalid(obj.rot),'Motor SN must be a valid number; motor handle may have been deleted. Check motor serial number/APT Config') -% Continually collect and plot PM and APD data until the user hits "Stop acquisition", which flips -% obj.acquire to false -while obj.acquire - obj.pm_data = [obj.pm_data, obj.PM100.get_power('MW')]; - obj.apd_data = [obj.apd_data, obj.counter.singleShot(obj.counter.dwell, obj.nsamples)]; - [~, sort_index] = sort(obj.pm_data); - plot(ax(1),obj.pm_data(sort_index),obj.apd_data(sort_index)) - xlabel(ax(1),'Input Power (mW)') - ylabel(ax(1), 'Output Counts') - plot(ax(2),obj.pm_data) - ylabel(ax(2),'Input Power (mW)') - xlabel(ax(2), 'Collection Bins') + % Instantiate data object + Nangles = length(obj.angle_list); + obj.data.intensity = nan(1,Nangles); + + % Setup graphics + y = NaN(1,Nangles); + hold(ax,'on'); + plotH(1) = plot(obj.angle_list, y,'color', 'k','parent',ax); + ylabel(ax,'Counts (cps)'); + xlabel(ax,['Angle (' char(176) ')']); + yyaxis(ax, 'left'); + + try + % Home rotation mount + + if ~obj.rot.Homed + status.String = 'Homing motor'; drawnow; + + obj.rot.home(); + pause4Move(obj, obj.motor_home_time); + end + + % Sweep through polarisation and get spectra + for i = 1:Nangles + theta = obj.angle_list(i); + if ~isempty(obj.rot) + status.String = sprintf( 'Navigating to %g (%i/%i)', theta, ... + i, Nangles); drawnow; + obj.rot.move(theta); + else + pause(5) + end + pause4Move(obj, obj.motor_move_time); + status.String = sprintf( 'Measuring at %g (%i/%i)', theta, ... + i, Nangles); drawnow; + + % Measure count rate + obj.data.intensity(i) = ctr.singleShot(obj.exposure, 1); + + plotH(1).YData = obj.data.intensity; + drawnow; assert(~obj.abort_request,'User aborted'); + end + + catch err + end + % CLEAN UP CODE % + if exist('err','var') + % HANDLE ERROR CODE % + rethrow(err) + end end -set(statusH, 'string', 'Acquisition complete!'); -% Set "abort" status if the data structures are empty -if isempty(obj.pm_data) && isempty(obj.apd_data) - set(statusH,'string','Aborted!'); +% Wait until motor stops moving, or timeout +function pause4Move(obj,maxTime) + t = tic; + while (obj.rot.Moving || ~obj.rot.Homed) && (toc(t) < maxTime) + drawnow + if toc(t) > maxTime + error('Motor timed out while moving') + end + end end -end \ No newline at end of file diff --git a/Modules/+Stages/MAX302motors.m b/Modules/+Stages/MAX302motors.m index 8f67226a5..cf9d47781 100644 --- a/Modules/+Stages/MAX302motors.m +++ b/Modules/+Stages/MAX302motors.m @@ -11,9 +11,9 @@ prefs = {'X_Motor','Y_Motor','Z_Motor','direction'}; end properties(SetObservable,AbortSet) - X_Motor = @Drivers.APTMotor.getAvailMotors; % Motor serial number - Y_Motor = @Drivers.APTMotor.getAvailMotors; % Motor serial number - Z_Motor = @Drivers.APTMotor.getAvailMotors; % Motor serial number + X_Motor = Prefs.MultipleChoice('help_text','Serial number of APT motor controlling the X axis','set','set_motor_serial_number','allow_empty',true) + Y_Motor = Prefs.MultipleChoice('help_text','Serial number of APT motor controlling the Y axis','set','set_motor_serial_number','allow_empty',true) + Z_Motor = Prefs.MultipleChoice('help_text','Serial number of APT motor controlling the Z axis','set','set_motor_serial_number','allow_empty',true) direction = [1 1 1]; end properties(SetAccess=private,SetObservable,AbortSet) @@ -45,6 +45,13 @@ methods(Access=private) function obj = MAX302motors() obj.loadPrefs; + + % Find available motor serial numbers + for motor = ["X_Motor","Y_Motor","Z_Motor"] + mp = obj.get_meta_pref(motor); + mp.choices = Drivers.APTMotor.getAvailMotors(); % set new choices + obj.set_meta_pref(motor, mp); + end end end % Callback functions for APTMotor