Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Modules/+Drivers/APTMotor.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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,~)
Copy link
Owner

Choose a reason for hiding this comment

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

obj.motor_serial_number = val; is now unnecessary (below).

val_as_double = str2double(val); % must be double to instantiate motor
assert(~isnan(val_as_double),'Motor SN must be a valid number.')

Expand Down
149 changes: 69 additions & 80 deletions Modules/+Experiments/@Saturation/Saturation.m
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Owner

Choose a reason for hiding this comment

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

Does this really still support manually moving HWP?


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);
Comment on lines +10 to +11
Copy link
Owner

Choose a reason for hiding this comment

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

Why not make these multiple choice? You can get the nidaq in/out lines from the device. Also, why don't you have to set the nidaq device anywhere O_O?


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)
Copy link
Owner

Choose a reason for hiding this comment

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

line can be deleted

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);
Copy link
Owner

Choose a reason for hiding this comment

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

str2num won't error on bad input. You should check for an empty array before assigning to angle_list.
Consider:

a = str2num('abc');

a will be [].

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 '<None>', 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
Comment on lines +77 to +79
Copy link
Owner

Choose a reason for hiding this comment

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

unreachable because of assert before.

% 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
end
20 changes: 20 additions & 0 deletions Modules/+Experiments/@Saturation/instance.m
Original file line number Diff line number Diff line change
@@ -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
104 changes: 71 additions & 33 deletions Modules/+Experiments/@Saturation/run.m
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Owner

Choose a reason for hiding this comment

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

not a big deal, but might as well not waste what could be a large vector. y seems to be only used in the plot function.

hold(ax,'on');
plotH(1) = plot(obj.angle_list, y,'color', 'k','parent',ax);
Copy link
Owner

Choose a reason for hiding this comment

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

if only one plotH, why index into it? Also why specify color (not an issue, just curious)?

ylabel(ax,'Counts (cps)');
xlabel(ax,['Angle (' char(176) ')']);
yyaxis(ax, 'left');
Copy link
Owner

Choose a reason for hiding this comment

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

Why is this being called? I only see one plot.


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
Comment on lines +41 to +47
Copy link
Owner

Choose a reason for hiding this comment

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

Assert above would prevent the else from ever happening. This seems to be the only thing that might give a user access to manually do it. But so many other things would break as they currently stand. I recommend just removing manual mode for now.

The easiest way to fix this would be to actually create a manual motor. Then, never change the code, but simply drop in a different motor that calls questdlg.

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
Comment on lines +71 to +73
Copy link
Owner

Choose a reason for hiding this comment

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

The chance that line 73 executes is extremely tiny (toc(t) would have had to go from < maxTime to > maxTime with only a drawnow between)! Your while loop is set to exit if maxTime is exceeded without erroring. This seems problematic?

error('Motor timed out while moving')
end
end
end
end
13 changes: 10 additions & 3 deletions Modules/+Stages/MAX302motors.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down