-
Notifications
You must be signed in to change notification settings - Fork 6
New saturation #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
New saturation #112
Changes from all commits
8457111
00eca7f
44c370c
0eedfa9
35fc477
dff6c42
d397460
f09902d
070568b
1c76634
ea109e1
4b84c9a
8219c35
e5d6be6
474d0ce
af182bd
fa24f3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 a = str2num('abc');
|
||
| 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
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unreachable because of |
||
| % 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 | ||
| 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 |
| 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); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| hold(ax,'on'); | ||
| plotH(1) = plot(obj.angle_list, y,'color', 'k','parent',ax); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assert above would prevent the 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 |
||
| 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
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The chance that line 73 executes is extremely tiny ( |
||
| error('Motor timed out while moving') | ||
| end | ||
| end | ||
| end | ||
| end | ||
There was a problem hiding this comment.
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).