Last active
November 15, 2024 17:55
-
-
Save FilipDominec/3ac56bde9c462b76d7a5edcc00e75a30 to your computer and use it in GitHub Desktop.
Streak Camera Analysis python snippet for nihilnovi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SIDEPLOTS = 0.0 # defaults 0.15 of width & height; set to 0 to disable them | |
FONTSIZE = 20 | |
SIDEPLOTS = 0.15; FONTSIZE = 18 # defaults 0.15 of width & height; set to 0 to disable them | |
SIDELEGENDS = False # True is useful for small fonts & big plots only | |
SAVEPLOT = True | |
SAVEDECAYS, SAVESPECTRA = False, False # for further processing | |
import matplotlib.pyplot as plt | |
plt.rcParams.update({'font.size': FONTSIZE}) | |
import pathlib | |
## Data preprocessing | |
all = np.array(ys) | |
plot_title = '_'.join(sharedlabels).rsplit('sample')[-1] # all info is in the shared labels | |
print('plot_title',plot_title) | |
if 'ns_' in plot_title: t_unit, t_scale_ns = 'ns', 1 | |
elif 'us_' in plot_title: t_unit, t_scale_ns = 'us', 1e3 | |
elif 'ms_' in plot_title: t_unit, t_scale_ns = 'ms', 1e6 | |
if all.shape == (641,481): ## "DAT" renamed from "DAC" format that contains row/column labels (newer) | |
ys = all[-2:1:-1, 1:] #- 0.02 ## manual subtraction of background | |
tt = all[0, 1:] * t_scale_ns # time axis (ns) | |
# wavelength axis (nm) with manual calibration | |
if '136A' in plot_title: | |
wl = all[-2:1:-1, 0]+7 | |
elif 'new' in plot_title: | |
wl = all[-2:1:-1, 0] | |
else: | |
wl = all[-2:1:-1, 0]-11 | |
else: # older raw DAT format that only contains data - and axes are to be described elsewhere | |
wl = np.linspace(313,580,all.shape[0]) | |
t1_from_label = plot_title.split(t_unit+'_')[0].rsplit('_',1)[1] | |
tt = np.linspace(0, float(t1_from_label) * t_scale_ns, all.shape[1]) | |
ys = all[::-1,:] | |
timespan = tt[-1]-tt[0] #diag: print(tt.shape, tt); print(wl.shape, wl) | |
## Normalize and smooth data | |
if 'ND' in plot_title: | |
NDfilter = float(plot_title.split('ND')[-1].split('p',1)[0])/100 | |
else: NDfilter = 1 | |
ys /= NDfilter | |
def convolve_without_edge_artifacts(ar, ker): | |
# at edges, the curve won't be bent towards 0, but may become more noisy | |
norm = np.convolve(np.ones_like(ar), ker, mode='same') | |
return np.convolve(ar, ker, mode='same') / norm | |
smooth_points = 10 # Select width for the (truncated-Gaussian) smoothing kernel | |
smooth_kernel = 2**-np.linspace(-3,3,smooth_points)**2 # Or use any function you like | |
smooth_kernel /= np.sum(smooth_kernel) # Roughly maintain the curve height | |
for axis in (0,1): # For 3D data, replace with (0,1,2) etc. | |
ys = np.apply_along_axis(convolve_without_edge_artifacts, axis, np.array(ys), | |
smooth_kernel) # these *args and **kwargs are passed to the chosen func | |
## Data calibration | |
spectral_mask_sync = np.logical_and(wl>300, wl<400) # sync to peak in UV (i.e. NBE or fastest QW) | |
ssum = ys[spectral_mask_sync,:].sum(axis=0) | |
tt -= tt[np.argmax(ssum)] # shift zero time to maximum intensity time | |
print("Time extent", tt[0], tt[-1] ) | |
## Plot preparation | |
if SIDEPLOTS: | |
fig.delaxes(ax) | |
gs = fig.add_gridspec(2, 2, width_ratios=(1-SIDEPLOTS, SIDEPLOTS), height_ratios=(SIDEPLOTS, 1-SIDEPLOTS), | |
wspace=0., hspace=0., left=0.18, right=.985, bottom=0.1, top=.985) | |
ax = fig.add_subplot(gs[1, 0]) | |
ax_xdecays = fig.add_subplot(gs[0, 0], sharex=ax) | |
ax_xdecays.tick_params(labelbottom=False, direction="in", labelsize=FONTSIZE-3) | |
ax_xdecays.grid() | |
ax_yspectra = fig.add_subplot(gs[1, 1], sharey=ax) | |
ax_yspectra.tick_params(labelleft=False, labelsize=FONTSIZE-3) | |
ax_yspectra.grid() | |
axtopright = fig.add_axes([1.05-SIDEPLOTS, 1.02-SIDEPLOTS, 0.03, SIDEPLOTS-.04]) | |
#<S-F2>axtopright = fig.add_axes([0.87, 0.89, 0.06, 0.15]) # x0, y0, x-width, y-height (rel to fig) | |
## Main streak-trace plot | |
import matplotlib.colors as colors | |
streak_map = ax.imshow(ys[::-1,:], cmap='turbo', | |
extent=[tt[0], tt[-1], wl[0], wl[-1],], aspect='auto', # aspect=.003, | |
norm=colors.PowerNorm(gamma=.5)) | |
ax.set_xlabel('time (ns)') | |
ax.set_ylabel('wavelength (nm)') | |
ax.grid(alpha=0) | |
from matplotlib.lines import Line2D | |
x0,y0,xw,yw = ax.get_position().bounds # prepare for on-axes coloring lines | |
## Top-right color bar | |
if SIDEPLOTS: | |
axtopright.set_title(f'raw I (a.u.) ' if SIDELEGENDS else '', rotation='vertical', x=-0.24, y=.5, | |
fontsize=int(FONTSIZE*.8), color='k', verticalalignment='center') | |
axtopright.tick_params(axis='both', which='major', labelsize=int(FONTSIZE*.8)) | |
cb = fig.colorbar(streak_map, cax=axtopright, shrink=0.7) | |
cb.ax.minorticks_on() | |
# Decay curves on top | |
dc_dir = pathlib.Path('decaycurves/'+plot_title+'_decaycurves') | |
dc_dir.mkdir(parents=True, exist_ok=True) | |
dx = -0.003 # spacing from the vert axis | |
if 'sample857' in sharedlabels or 'sample857new' in sharedlabels: | |
spectral_bands_defs = ((350,370,'darkviolet','GaN NBE'), | |
(380, 405,'deepskyblue', 'm-plane QW'), (425, 510,'r', 'polar QW')) | |
#elif 'sample136A' in sharedlabels: | |
#spectral_bands_defs = ((350,370,'gray','GaN NBE'), | |
#(380, 410,'k', 'QW'), (470, 510,'orange', 'DB')) | |
else: | |
spectral_bands_defs = ((350,370,'gray','GaN NBE'), | |
(390, 440,'k', 'QW'), (470, 510,'orange', 'DB')) | |
ymax = 0 | |
for (l0,l1,c,label) in spectral_bands_defs: | |
spectral_mask = np.logical_and(wl>l0, wl<l1) | |
if sum(spectral_mask): | |
i = ys[spectral_mask,:].mean(axis=0) | |
if SIDEPLOTS: | |
ax_xdecays.plot(tt, i, label=f"$\\lambda\\in ({l0:g}; {l1:g})$ nm: {label}", c=c) | |
if max(i)>ymax: ymax=max(i) | |
y0i,y1i = np.interp((l0,l1), (min(wl), max(wl)), (y0,y0+yw)) # outline segments along the graph axes | |
fig.add_artist(Line2D([x0+dx,x0+dx], [y0i,y1i],lw=3., color=c)) | |
if SAVEDECAYS: | |
np.savetxt(dc_dir/f'band_from_{l0:g}_to_{l1:g}nm.dat', np.vstack([tt,i/timespan]).T, fmt="%.8g", header='#t(ns) intensity(a.u.)') | |
if SIDEPLOTS: | |
ax_xdecays.set_ylabel('band $I(t)$') | |
if SIDELEGENDS: ax_xdecays.legend(loc='upper center') | |
ax_xdecays.set_yscale('log') | |
ax_xdecays.set_ylim(ymin=ymax/3.1e3, ymax=ymax*1.2) | |
# spectral curves on right side | |
ts_dir = pathlib.Path('timeslices/'+plot_title+'_timeslices') | |
ts_dir.mkdir(parents=True, exist_ok=True) | |
dy = -0.005 # spacing from the horiz axis | |
Imax = 0 | |
t_slice_milestones = [j*10**k for k in range(-1,8) for j in (0.1,0.3)] | |
for (t0,t1,color) in zip(t_slice_milestones[:-1], t_slice_milestones[1:], 'mrygcb'*10): | |
temporal_mask = np.logical_and(tt>t0, tt<t1) | |
if sum(temporal_mask) > 5: | |
i = ys[:,temporal_mask].mean(axis=1) | |
if SIDEPLOTS: | |
ax_yspectra.plot(i, wl, label=f"$t\\in ({t0:g}; {t1:g})$ ns", c=color) | |
if max(i)>Imax: Imax=max(i) | |
x0i,x1i = np.interp((t0,t1), (min(tt), max(tt)), (x0,x0+xw)) | |
fig.add_artist(Line2D([x0i,x1i],[y0+dy,y0+dy], lw=3., color=color)) | |
if SAVESPECTRA: | |
np.savetxt(ts_dir/f'slice_to_{t1:g}ns.dat', np.vstack([wl,i/timespan]).T, fmt="%.8g") | |
if SIDEPLOTS: | |
ax_yspectra.set_xlabel('$I(λ)$') | |
if SIDELEGENDS: ax_yspectra.legend() | |
ax_yspectra.set_xscale('log') | |
ax_yspectra.set_xlim(xmin=Imax/1.1e3, xmax=Imax*1.2) | |
ax_yspectra.xaxis.set_ticks([1e-1,1e1]) # reduced to just two tick labels for a narrow plot | |
if SAVEPLOT: | |
tosave.append(plot_title+'_streakcam_small.png') ## whole graph will be saved as PNG | |
tosave.append(plot_title+'_streakcam_small.pdf') ## whole graph will be saved as PDF | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is how a sample plot in https://github.com/FilipDominec/nihilnovi/ looks like with
SIDEPLOTS = 0.15; FONTSIZE = 18
.