Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
72 changes: 62 additions & 10 deletions autodeer/DEER_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,13 +680,17 @@ def DEERanalysis_plot_pub(results, ROI=None, fig=None, axs=None,title=None,cmap=
axs = fig.subplots(*subplots,subplot_kw={},gridspec_kw={'hspace':0})

for i,result in enumerate(results):
# resale so that model max is 1
rescale_factor = result.model.max()
result.model /= rescale_factor
result.Vexp /= rescale_factor
axs[0].plot(result.t,result.Vexp, '.',alpha=0.5,color=cmap[i],mec='none')
axs[0].plot(result.t,result.model, '-',alpha=1,color=cmap[i], lw=2)
if n_datasets > 1:
c = cmap[i]
else:
c= cmap[1]
axs[0].plot(result.t,background_func(result.t, result), '--',alpha=1,color=c, lw=2)
axs[0].plot(result.t,background_func(result.t, result)/rescale_factor, '--',alpha=1,color=c, lw=2)

axs[0].set_xlabel(r'Time / $\mu s$')
if orientation.lower() == 'vertical':
Expand Down Expand Up @@ -1161,6 +1165,11 @@ def shift_m11_01(x):
"""
return (-x + 1) / 2
def build_profile(pulses,freqs=None,resonator=None):
"""
Builds the excitation probability profile for a product of a given set of pulses.

For pi pulses, the Mz component (rescaled to 0-1) is used, while for pi/2 pulses, the transverse component (sqrt(Mx^2 + My^2)) is used.
"""

if freqs is None:
freqs = np.linspace(-0.3,0.3,100)
Expand All @@ -1176,11 +1185,12 @@ def build_profile(pulses,freqs=None,resonator=None):
tmp_excite_profile = -1*tmp_excite_profile+ 1
excite_profile *= tmp_excite_profile
else:
excite_profile = pulses.exciteprofile(freqs=freqs,resonator=resonator)[:,2].real
if pulses.flipangle.value == np.pi:
excite_profile = pulses.exciteprofile(freqs=freqs,resonator=resonator)[:,2].real
excite_profile = shift_m11_01(excite_profile)
elif pulses.flipangle.value == np.pi/2:
excite_profile = -1*excite_profile+ 1
excite_profile_M = pulses.exciteprofile(freqs=freqs,resonator=resonator)[:,2]
excite_profile = np.sqrt(excite_profile_M[:,0].real**2 + excite_profile_M[:,1].real**2) # transverse component, sqrt(Mx^2 + My^2)

return excite_profile

Expand Down Expand Up @@ -1219,6 +1229,7 @@ def calc_functional(Fieldsweep, pump_pulse, exc_pulse, ref_pulse,resonator=None,

fieldsweep_profile = resample_and_shift_vector(fieldsweep_profile, f, spec_shift)
fieldsweep_profile /= np.trapz(fieldsweep_profile,f) # normalise the fieldsweep profile
resonator_profile = np.interp(f, resonator.freqs-resonator.freq_c, resonator.profile)


obs_sequence = [exc_pulse]
Expand All @@ -1241,9 +1252,34 @@ def calc_functional(Fieldsweep, pump_pulse, exc_pulse, ref_pulse,resonator=None,
P_obs = np.trapz(exc_profile*fieldsweep_profile,f) / np.trapz(fieldsweep_profile,f)
P_none = 1 - P_pump - P_obs

return 2*P_pump*P_obs
# coupling_factor = np.trapz(resonator_profile*exc_profile,f)/np.trapz(exc_profile,f)

return 2*P_pump*P_obs #* coupling_factor

def calc_coupling_factor(Fieldsweep, pump_pulse, exc_pulse, ref_pulse, respro,num_ref_pulses=2,**kwargs):
resonator = respro
fieldsweep_fun = Fieldsweep.func_freq
f = np.linspace(-0.3,0.3,100)
fieldsweep_profile = fieldsweep_fun(f)

obs_sequence = [exc_pulse]
for i in range(num_ref_pulses):
obs_sequence.append(ref_pulse)

def calc_est_signal(Fieldsweep, pump_pulse, exc_pulse, ref_pulse, respro=None, num_ref_pulses=2, **kwargs):
pump_profile = build_profile(pump_pulse,f,resonator)
exc_profile = build_profile(obs_sequence,f,resonator)
dead_profile = pump_profile * exc_profile
pump_profile -= dead_profile
exc_profile -= dead_profile
pump_profile[pump_profile <0] = 0
exc_profile[exc_profile <0] = 0

resonator_profile = np.interp(f, resonator.freqs-resonator.freq_c, resonator.profile)
coupling_factor = np.trapz(resonator_profile*exc_profile,f)/np.trapz(exc_profile,f)
return coupling_factor


def calc_est_signal(Fieldsweep, pump_pulse, exc_pulse, ref_pulse, respro=None, num_ref_pulses=2,n_pump_pulses=1, **kwargs):
"""
Calculate the estimated total signal from the EPR spectrum, the pulses and the resonator profile.

Expand Down Expand Up @@ -1272,12 +1308,19 @@ def calc_est_signal(Fieldsweep, pump_pulse, exc_pulse, ref_pulse, respro=None, n
fieldsweep_fun = Fieldsweep.func_freq
f = np.linspace(-0.3,0.3,100)
fieldsweep_profile = fieldsweep_fun(f)
if resonator is not None:
resonator_profile = np.interp(f, resonator.freqs-resonator.freq_c, resonator.profile)

obs_sequence = [exc_pulse]
for i in range(num_ref_pulses):
obs_sequence.append(ref_pulse)

if n_pump_pulses > 1:
pump_sequence = [pump_pulse] * n_pump_pulses
else:
pump_sequence = pump_pulse

pump_profile = build_profile(pump_pulse,f,resonator)
pump_profile = build_profile(pump_sequence,f,resonator)
exc_profile = build_profile(obs_sequence,f,resonator)
dead_profile = pump_profile * exc_profile
pump_profile -= dead_profile
Expand All @@ -1289,7 +1332,12 @@ def calc_est_signal(Fieldsweep, pump_pulse, exc_pulse, ref_pulse, respro=None, n
P_obs = np.trapz(exc_profile*fieldsweep_profile,f) / np.trapz(fieldsweep_profile,f)
P_none = 1 - P_pump - P_obs

return (2*P_pump*P_obs + P_obs**2 + 2*P_obs*P_none)
if resonator is None:
coupling_factor = 1
else:
coupling_factor = np.trapz(resonator_profile*exc_profile,f)/np.trapz(exc_profile,f)

return (2*P_pump*P_obs + 2*P_obs**2 + 2*P_obs*P_none) * coupling_factor

def calc_est_modulation_depth(Fieldsweep, pump_pulse, exc_pulse, ref_pulse, respro=None, num_ref_pulses=2,n_pump_pulses=1, **kwargs):
"""
Expand Down Expand Up @@ -1346,7 +1394,7 @@ def calc_est_modulation_depth(Fieldsweep, pump_pulse, exc_pulse, ref_pulse, resp
P_obs = np.trapz(exc_profile*fieldsweep_profile,f) / np.trapz(fieldsweep_profile,f)
P_none = 1 - P_pump - P_obs

return (2*P_pump*P_obs)/(2*P_pump*P_obs + P_obs**2 + 2*P_obs*P_none)
return (2*P_pump*P_obs)/(2*P_pump*P_obs + 2*P_obs**2 + 2*P_obs*P_none)


def plot_overlap(Fieldsweep, pump_pulse, exc_pulse, ref_pulse,spectrum_shift=0, filter=None, resonator=None, num_ref_pulses=2,n_pump_pulses=1, axs=None, fig=None,**kwargs):
Expand Down Expand Up @@ -1378,6 +1426,8 @@ def plot_overlap(Fieldsweep, pump_pulse, exc_pulse, ref_pulse,spectrum_shift=0,
The axes to plot on, by default None
fig : matplotlib.figure, optional
The figure to plot on, by default None
cmap : list, optional
The colormap to use for the pump and excitation profiles, by default primary_colors

"""

Expand Down Expand Up @@ -1408,6 +1458,8 @@ def plot_overlap(Fieldsweep, pump_pulse, exc_pulse, ref_pulse,spectrum_shift=0,
pump_profile[pump_profile <0] = 0
exc_profile[exc_profile <0] = 0

cmap = kwargs.get('cmap',primary_colors[0])

if axs is None and fig is None:
fig, axs = plt.subplots(1,1,figsize=(5,5), layout='constrained')
elif axs is None:
Expand All @@ -1416,8 +1468,8 @@ def plot_overlap(Fieldsweep, pump_pulse, exc_pulse, ref_pulse,spectrum_shift=0,
fieldsweep_profile /= np.abs(fieldsweep_profile).max()
f -= spectrum_shift
axs.plot(f*1e3,fieldsweep_profile, label = 'Fieldsweep', c='k')
axs.fill_between(f*1e3, pump_profile*fieldsweep_profile, label = 'Pump Profile', alpha=0.5, color=primary_colors[0])
axs.fill_between(f*1e3, exc_profile*fieldsweep_profile, label = 'Observer Profile', alpha=0.5, color=primary_colors[1])
axs.fill_between(f*1e3, pump_profile*fieldsweep_profile, label = 'Pump Profile', alpha=0.5, color=cmap[0])
axs.fill_between(f*1e3, exc_profile*fieldsweep_profile, label = 'Observer Profile', alpha=0.5, color=cmap[1])

if resonator is not None:
axs2 = axs.twinx()
Expand Down
10 changes: 6 additions & 4 deletions autodeer/Logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ def setup_logs(folder: str):

autoDEER_log = logging.getLogger('autoDEER')
interface_log = logging.getLogger('interface')
logHandler_core = handlers.TimedRotatingFileHandler(
os.path.join(folder,'autoDEER.log'), when='D', backupCount=4)
# logHandler_core = handlers.TimedRotatingFileHandler(
# os.path.join(folder,'autoDEER.log'), when='D', backupCount=4)
logHandler_core = handlers.FileHandler(os.path.join(folder,'autoDEER.log'))
logHandler_core.setFormatter(formatter)
autoDEER_log.setLevel(logging.INFO)
autoDEER_log.addHandler(logHandler_core)
Expand All @@ -54,8 +55,9 @@ def setup_logs(folder: str):
QTHandler.setFormatter(DictFormater())
autoDEER_log.addHandler(QTHandler)

logHandler_hardware = handlers.TimedRotatingFileHandler(
os.path.join(folder,'interface.log'), when='D', backupCount=4)
# logHandler_hardware = handlers.TimedRotatingFileHandler(
# os.path.join(folder,'interface.log'), when='D', backupCount=4)
logHandler_hardware = handlers.FileHandler(os.path.join(folder,'interface.log'))
logHandler_hardware.setFormatter(formatter)
interface_log.setLevel(logging.INFO)
interface_log.addHandler(logHandler_hardware)
Expand Down
6 changes: 3 additions & 3 deletions autodeer/gui/autoDEER_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ def run_long_deer(self):
DEER_crit = DEERCriteria(mode="high",verbosity=2,update_func=self.signals.longdeer_update.emit)
total_crit = [DEER_crit, self.EndTimeCriteria]
end_signal = self.signals.longdeer_result.emit
self.run_deer(total_crit,end_signal, dt=16,shot=50,averages=1e3)
self.run_deer(total_crit,end_signal, dt=16,shot=50,averages=1000)

def run_single_deer(self):
# Run a DEER experiment background measurement
Expand All @@ -362,7 +362,7 @@ def run_single_deer(self):
SNR_crit = SNRCriteria(150,verbosity=2)
total_crit = [SNR_crit, self.EndTimeCriteria]
end_signal = self.signals.longdeer_result.emit
self.run_deer(total_crit,end_signal, dt=16,shot=50,averages=1e3)
self.run_deer(total_crit,end_signal, dt=16,shot=50,averages=1000)

def run_deer(self, end_criteria, signal, dt=16, shot=50, averages=1000,):

Expand Down Expand Up @@ -470,7 +470,7 @@ def tune_pulses(self):
ref_pulse = self.pulses['ref_pulse']
exc_pulse = self.pulses['exc_pulse']
det_event = self.pulses['det_event']
shots = np.max([int(10*self.noise_mode), 2])
shots = np.max([int(25*self.noise_mode), 10])
self.signals.status.emit('Tuning pulses')
exc_pulse = self.interface.tune_pulse(exc_pulse, mode="amp_nut", B=self.freq/self.gyro,freq=self.freq,reptime=self.reptime,shots=shots)
ref_pulse = self.interface.tune_pulse(ref_pulse, mode="amp_nut", B=self.freq/self.gyro,freq=self.freq,reptime=self.reptime,shots=shots)
Expand Down
Binary file added autodeer/gui/resources/autoDEER.ico
Binary file not shown.
14 changes: 9 additions & 5 deletions autodeer/reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from autodeer.DEER_analysis import DEERanalysis_plot, plot_overlap, DEERanalysis_plot_pub
from pyepr import plot_1Drelax
from autodeer.colors import primary_colors

from svglib.svglib import svg2rlg
from io import BytesIO
Expand Down Expand Up @@ -243,7 +244,8 @@ def create_report(save_path, Results:dict, SpectrometerInfo:dict=None, UserInput
report._build()
pass

def combo_figure(EDFS, respro, pulses:dict, relaxation:list, init_deer, long_deer ,title=None, fig=None):
def combo_figure(EDFS, respro, pulses:dict, relaxation:list, init_deer, long_deer ,title=None, fig=None,
cmap=None):
"""
Creates a 2x2 summary figure.
- The top left plot is the EDFS and resonator profile, overlapped with the optimised pulses.
Expand All @@ -270,6 +272,8 @@ def combo_figure(EDFS, respro, pulses:dict, relaxation:list, init_deer, long_dee


"""
if cmap is None:
cmap = primary_colors
if fig is None:
fig = plt.figure(figsize=(10, 10),constrained_layout=True)
subfigs = fig.subfigures(2, 2, height_ratios=(4,6), width_ratios=(1,1),wspace=.0)
Expand All @@ -293,15 +297,15 @@ def combo_figure(EDFS, respro, pulses:dict, relaxation:list, init_deer, long_dee
fig.suptitle(title,size=20)

subfigs[0].suptitle('Pulse Setup',size=15)
plot_overlap(EDFS, pulses['pump_pulse'], pulses['exc_pulse'],pulses['ref_pulse'],respro=respro,axs=ax1,fig=subfigs[0]);
plot_overlap(EDFS, pulses['pump_pulse'], pulses['exc_pulse'],pulses['ref_pulse'],respro=respro,axs=ax1,fig=subfigs[0],cmap=cmap);
subfigs[0].get_axes()[0].set_ylabel(' ',labelpad=8)
subfigs[0].axes[0].set_xlim(-300,100)
subfigs[1].suptitle('Relaxation',size=15)
plot_1Drelax(*relaxation,axs=ax2,fig=subfigs[1]);
plot_1Drelax(*relaxation,axs=ax2,fig=subfigs[1],cmap=cmap);
subfigs[2].suptitle('Intial DEER',size=15)
DEERanalysis_plot_pub(init_deer[0],ROI=init_deer[0].ROI,axs=ax3,fig=subfigs[2]);
DEERanalysis_plot_pub(init_deer[0],ROI=init_deer[0].ROI,axs=ax3,fig=subfigs[2],cmap=cmap);
subfigs[3].suptitle('Final DEER',size=15)
DEERanalysis_plot_pub(long_deer,axs=ax4,fig=subfigs[3]);
DEERanalysis_plot_pub(long_deer,axs=ax4,fig=subfigs[3],cmap=cmap);

subfigs[0].get_axes()[0].set_ylabel(' ',labelpad=8)
subfigs[0].get_axes()[0].yaxis.set_major_formatter(FormatStrFormatter('%.2f'))
Expand Down
4 changes: 4 additions & 0 deletions docsrc/source/releasenotes.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Release Notes
=============
Version 1.0.1 (2025-11-23):
++++++++++++++++++++++++++++
- Fixed the definition of the exciation probabiklity profile for pi/2 pulses to be the transverse component (sqrt(Mx^2 + My^2)) rather than (-Mz + 1), which was incorrect.
-

Version 1.0.0 (2025-09-12):
++++++++++++++++++++++++++++
Expand Down
Loading