mainDir=scriptFolder# automaticaly takes the folder were the script is as main folder (change here if you don't want to use current folder)
dirList=[# list of sub-folder where the images are stored
r"\25-02-12 945_1c\1400nm\QWP0",
r"\25-02-12 945_1c\1400nm\QWP45",
r"\25-02-12 945_1c\1400nm\QWP315",
]
saveDir=mainDir+"/output/"# output folder by default creates an output folder in the main folder
ifnotos.path.exists(saveDir):# create the output folder in it doesn't exist
os.makedirs(saveDir)
binFactor=4# binning factor of the imported image set (must be the same for all images)
pxSizeX=0.3255E-6*binFactor# m/px
pxSizeY=0.2865E-6*binFactor# m/px
repRate=100e3#Hz
LOWSTATE="P+"# initial state with lowest grey level
HIGHSTATE="P-"# initial state with highest grey level
TAG_INDEX=699# tag index to recuperate the metadata stored in the tiff images (Note : only use for images generated by this setup not implemented yet in other setups)
propDef={# descriptor of propreties to import from metadata. parameter name to be imported : (name,type) in metadata
"Power":('Pump power',float),
"QWP":('Pump QWP angle',float),
# "Compressor" : ('Compressor',int),
"Initial_State":('Initial state',str),
"Number_pulse":('Number of pulse',int),
"Wavelength":('Pump wavelength',int)
}
Params=namedtuple('Params',list(propDef.keys()))# create the Params class from the propDef descriptor to store images' propreties
Params.__repr__=lambdaself:'Params:\n'+tabulate(self._asdict().items())# change Params string representation to be easier to read (Comment it out if you don't like it)
communParam=None
rawImages={}# dictonary storing the parameters as the key (namedtuple) and list of images as values
bkgImage={}# dictonary storing the parameters as the key (namedtuple) and list of backgrounds as values
forsubDirindirList:
imageDir=mainDir+subDir
forfilenameinfilter(lambdax:x.endswith('.tiff'),os.listdir(imageDir)):# iterate over the filenames ending in .tiff in the curent directory imageDir
images,propLists=openTiffImagesAndPropreties(os.path.join(imageDir,filename))# open images and propreties from file
forim,propTaginzip(images,propLists):# iterate over the stack of image and propreties (for multipage tiff)
communParam=[aif (communParam==NoneoraincommunParam)else{**a,"value":"\x1b[35m\x1b[1mvariable\x1b[0m"}forainpropTag]#compare the metadata to communParam in order to gather commun parameters and flag variable ones
prop=Params(**{k:v[1](next(xforxinpropTagifx['Decription']==v[0])["value"])fork,vinpropDef.items()})# create the propreties name tuple storing the parameters of the image to be used as a key for the dictonary
if"Bkg"infilename:# seperate background images (must have Bkg in their name)
ifpropnotinbkgImage:# check if the key already exist in the bkgImage dictonary
bkgImage[prop]=[]# if not create a new entry with a empty list
bkgImage[prop].append(im)# append the background images in bkgImage dictonary
else:# seperate images from background
ifpropnotinrawImages:# check if the key already exist in the rawImages dictonary
rawImages[prop]=[]# if not create a new entry with a empty list
rawImages[prop].append(im)# append the images in rawImages dictonar
communParam=[{**a,"Param":"\x1B[31m\x1b[1mX\x1b[0m"}ifa['Decription']in[p[0]forpinpropDef.values()]else{**a,"Param":""}foraincommunParam]# add a red cross next to the parameters selected in propDef descriptor
print(tabulate([OrderedDict([(k,p[k])forkin['Decription','Short','value','units','Param']])forpincommunParam],# print the information about the loaded data from the metadata
colalign=('right','left','left','left','center'),
tablefmt="rst",
headers="keys"))
#%% get bkg Intensity
# gets an ROI from which the illumination's levels are ajusted
cv2.namedWindow("output",cv2.WINDOW_NORMAL)# create a cv2 window
ROIBoxInt=cv2.selectROI("output",cv2.cvtColor((rawImages[sorted(rawImages.keys())[-1]][0]/np.max(rawImages[sorted(rawImages.keys())[-1]][0])).astype('float32'),cv2.COLOR_GRAY2RGB))# display the image with the highest power and prompt for a ROI of an empty area
cv2.destroyAllWindows()# destroy the cv2 window
# function to get the mean grey level in the previously selcted ROI
cv2.namedWindow("output",cv2.WINDOW_NORMAL)# create a cv2 window
ROIBox=cv2.selectROI("output",cv2.cvtColor(normImages[sorted(normImages.keys())[-1]][1],cv2.COLOR_GRAY2RGB))# display the image with the highest power and prompt for a ROI for croping
# for k, g in itt.groupby(sortedKeys, keyfunc): # group data with commun keyfunc output (more info : https://docs.python.org/3/library/itertools.html#itertools.groupby)
# if subFilterFunc(k): # apply filter
# g = list(sorted(g)) # convert and sort from grouper type to list (if not the generator will empty itself after the first passage)
# figax = plt.subplots(2,1,sharex=True,figsize=(10,10)) # new plot with 2 axis
# figax[1][0].hist(np.ravel(gaussian_filter(imageDictIn[g[0]],sigmaGauss,axes=(1,2))),100,label=g[0].Initial_State,histtype="step") # plot the histogram of all pixels of the group of images the LOWSTATE after appling a gaussian filter
# figax[1][0].hist(np.ravel(gaussian_filter(imageDictIn[g[1]],sigmaGauss,axes=(1,2))),100,label=g[1].Initial_State,histtype="step") # plot the histogram of all pixels of the group of images the HIGHSTATE after appling a gaussian filter
# figax[1][0].set_yscale('log')
# figax[1][0].legend()
# figax[1][0].set_title(' '.join([str(a)+" = "+str(b) for a,b in zip(groups,k)])) # format the name from the group key
# figax[1][0].axvline(0)
# figax[1][0].axvline(1,color="orange")
# X = np.linspace(0, 1,100) # list of threshold
# Y = [np.sum(threshold<np.ravel(gaussian_filter(imageDictIn[g[0]],sigmaGauss,axes=(1,2)))) for threshold in X] # for each threshold count the number of pixel above the threshold for the LOWSTATE after appling a gaussian filter
# figax[1][1].plot(X,Y,label=g[0].Initial_State)
# Y = [np.sum(threshold>np.ravel(gaussian_filter(imageDictIn[g[1]],sigmaGauss,axes=(1,2)))) for threshold in X] # for each threshold count the number of pixel below the threshold for the HIGHSTATE after appling a gaussian filter
# figax[1][1].plot(X,Y,label=g[1].Initial_State)
# figax[1][1].set_yscale('log')
# figax[1][1].legend()
# figax[1][1].axvline(0)
# figax[1][1].axvline(1,color="orange")
# plt.show()
#%% plot histogram empty spot for detection threshold
sigmaGauss=1# sigma to use for the gaussian filter
# get an ROI to plot the histogram of empty spot to select the thresholds
cv2.namedWindow("HistROI",cv2.WINDOW_NORMAL)# create a cv2 window
ROIBoxInt=cv2.selectROI("HistROI",cv2.cvtColor((imageDictIn[sorted(imageDictIn.keys())[-1]][0]/np.max(imageDictIn[sorted(imageDictIn.keys())[-1]][0])).astype('float32'),cv2.COLOR_GRAY2RGB))# display the image with the highest power and prompt for a ROI of an empty area
cv2.destroyAllWindows()# destroy the cv2 window
# plot the histogram of the ROI of the images after appling a gaussian filter
# get enclosing circle from the inliers (more info : https://docs.opencv.org/3.4/dd/d49/tutorial_py_contour_features.html)
center2,radius2=cv2.minEnclosingCircle(inliers)
returncenter1,radius1,center2,radius2# return the center and radius of the circles the first one is the inner circle the second one is the outer circle
else:# if only one point return the coodinate of the point but zero radius
returntuple(X[0]),0,tuple(X[0]),0
else:# if empy return only zeros
return (0,0),0,(0,0),0
# get the circles of all images
circles=mapDictList(getCircles,imageDictIn)
#%% plot norm images and circles
# keyFilterFunc = lambda x:x.Initial_State=="P+" and x.QWP==45.0 and x.Number_pulse==1000
# FilterFunc = lambda x:x.QWP==0.0 # filter the image to display
# sortKey = lambda x:(x.Power,x.Number_pulse,x.QWP,x.Initial_State) # method of sorting of the images to be displayed
# for prop in sorted(filter(FilterFunc,circles.keys()),key=sortKey): # iterate over all filtered and sorted keys of circles
# for im,c in zip(imageDictIn[prop],circles[prop]): # iterate in parallele over the list of image and coresponding circle with a given key
# fig,axs = plt.subplots(1,3,figsize=(18,7)) # new plot with 3 axis
# plotIm(im,fig,axs[0]) # plot image from imageDictIn in the first axis
# binIm = (gaussian_filter(im,1)>ThresholdLow)*(prop.Initial_State==LOWSTATE) + (gaussian_filter(im,1)<ThresholdHigh)*(prop.Initial_State==HIGHSTATE) # get binary image based on the initial state and coresponding threshold
# for k, g in itt.groupby(sortedKeys, keyfunc): # group data with commun keyfunc output (more info : https://docs.python.org/3/library/itertools.html#itertools.groupby)
# if subFilterFunc(k): # apply filter
# figax = plt.subplots(figsize=(10,7)) # create new plot
# g = list(g) # convert from grouper type to list (if not the generator will empty itself after the first passage)
# keyfunc2 = lambda x:x.Initial_State # key for the sub grouping
# sortedKeys2 = list(sorted(g,key=keyfunc2)) # sort sub group key !! necessary for itt.groupby
# for k2, g2 in itt.groupby(sortedKeys2, keyfunc2): # sub-group grouped data with commun keyfunc2 output the data will be on the same plot but with different label
# g2 = list(g2) # convert from grouper type to list (if not the generator will empty itself after the first passage)
# # plot the sub grouped data point
# plotDictListValue(mapDictList(lambda prop,x:x[radiusSelect], circles), # create a sub dictionary of circle only selecting one of the circles
# Xaxis="Power", # name of the proprety to use as the X axis
# keyFilter=lambda x:x in g2, # only select point of the sub group
# figax=figax, # figure and axis to use
# logScale=True, # logaritmic Y scaling
# label=k2, # use the sub-grouping key as label
# title='\n'.join([str(a)+" = "+str(b) for a,b in zip(groups,k)]) # format title using the outer group keys
# )
# plt.show()
#%% export radius
# parameters = ["Compressor","Number_pulse","QWP","Initial_State","Power"] # list of propreties to use as column to discribe the output
parameters=["Number_pulse","QWP","Initial_State","Power"]# list of propreties to use as column to discribe the output
data=np.array(sorted([[getattr(k,param)forparaminparameters]+# retrive propreties to fill the columns
[k.Power/1E3/repRate,r[1]*pxSizeY,r[3]*pxSizeY]fork,vinsorted(circles.items(),key=lambdax:x[0])forrinv]))# for each data point calculate the pulse energy in J and the inner and outer radius in m
paramName=",".join(parameters)+",Ep,R1,R2"# format the columns name
# get the units of the selected column parameters and append the fixed ouput unit for pulse energy and radius
np.savetxt(os.path.join(saveDir,'Radius.csv'),data,header=paramName+'\n'+units,fmt='%s',delimiter=",")# export to .csv as str array and with the constructed header
#%% def inv gauss radius (Prefer the other fit method)
# for k, g in itt.groupby(sortedKeys, keyfunc): # group data with commun keyfunc output (more info : https://docs.python.org/3/library/itertools.html#itertools.groupby)
# try: # exception handeling if the fiting fails
# EpRList = np.array([[k.Power*1E-3/repRate,pxSizeY*cs[radiusSelect]] for k in g for cs in circles[k]]) # construct the data to be fited X: Energy per pulse [J] ; Y: Radius [m]
# popt, pcov = curve_fit(Rfunc, EpRList[:,0], EpRList[:,1],p0=(1e-5,100),maxfev=1000000) # fit with the previously defined function Rfunc. the initial guess need to be ajusted for the data if fitting fails
# plt.plot(EpRList[:,0], EpRList[:,1],".") # plot to be fitted data
# newEp = np.linspace(min(EpRList[:,0]),max(EpRList[:,0]),500) # X for ploting of the the fited function calculated from the min and max of the data
# newR = [Rfunc(ep,*popt) for ep in newEp] # Y for ploting of the the fited function calculaded at every X
# plt.plot(newEp,newR) # plot fited function
# plt.title(k) # use group key as title
# plt.show()
# fitParam[k] = popt # store the fit parameters into the fitParam dictonary
# except RuntimeError as err: # if fit fails
# print(err) # print error
# plt.plot(EpRList[:,0], EpRList[:,1],".") # plot the data without fit
# plt.title(k) # use group key as title
# plt.show()
#%% export inv gauss fit (Prefer the other fit method)
# # parameters = ["Compressor","Number_pulse","QWP","Initial_State"] # list of propreties to use as column to discribe the output
# parameters = ["Number_pulse","QWP","Initial_State"] # list of propreties to use as column to discribe the output
# # retrive propreties to fill the columns and append the fit parameters
# data = np.array(sorted([[getattr(k,param) for param in parameters] + list(v) for k,v in sorted(fitParam.items(),key=lambda x:x[0])]))
# # format the columns name
# paramName = ",".join(parameters) + ",Sigma,Fth"
# # get the units of the selected column parameters and append the fixed ouput unit for sigma and fluence threshold
# units = ",".join([next(filter(lambda x:x['Decription']==propDef[param][0],communParam))['units'] for param in parameters]) +\
# ',m,J/m2'
# np.savetxt(os.path.join(saveDir,'invGaussFit.csv'),data,header=paramName+'\n'+units,fmt='%s',delimiter=",") # export to .csv as str array and with the constructed header
radiusSelect=OUTERCERCLE# select circle radius to plot can be INNERCERCLE or OUTERCERCLE
fitParam={}
groups=["Number_pulse","QWP","Initial_State"]# group data based on the propreties
# groups = ["Compressor","Number_pulse","QWP","Initial_State"] # group data based on the propreties
subParams=namedtuple('subParams',groups)# create a subparameter nametuple class to store selected images' propreties
keyfunc=lambdax:subParams(**{key:getattr(x,key)forkeyingroups})# get keys of the grouped data
sortedKeys=list(sorted(circles.keys(),key=keyfunc))# sort dict keys !! necessary for itt.groupby
fork,ginitt.groupby(sortedKeys,keyfunc):# group data with commun keyfunc output (more info : https://docs.python.org/3/library/itertools.html#itertools.groupby)
try:# exception handeling if the fiting fails
oneOverEpRList=np.array([[repRate/k.Power/1E-3,pxSizeY*cs[radiusSelect]]forkingforcsincircles[k]ifcs[radiusSelect]>0])# construct the data to be fited excluding zero size radius Y: 1/(Energy per pulse) [J-1] ; X: Radius [m]
symDataList=np.vstack((np.flip(oneOverEpRList*[1,-1],axis=0),oneOverEpRList))# construct the symetric radius data for the fit
popt,pcov=curve_fit(OneOverEpFunc,symDataList[:,1],symDataList[:,0],p0=(1e-4,1E-6),maxfev=10000)# fit with the previously defined function OneOverEpFunc. The initial guess need to be ajusted for the data if fitting fails
plt.plot(symDataList[:,1],symDataList[:,0],".")# plot to be fitted data
newR=np.linspace(min(symDataList[:,1]),max(symDataList[:,1]),100)# X for ploting of the the fited function calculated from the min and max of the data
newOneOverEp=[OneOverEpFunc(r,*popt)forrinnewR]# Y for ploting of the the fited function calculaded at every X
plt.plot(newR,newOneOverEp)# plot fited function
plt.title(k)# use group key as title
plt.show()
fitParam[k]=np.array([popt[0],popt[1]/np.pi/popt[0]**2])# convert to sigma and fluence threshold and store the fit parameters into the fitParam dictonary
exceptRuntimeErroraserr:# if fit fails
print(err)# print error
#%% export gauss fit
# parameters = ["Compressor","Number_pulse","QWP","Initial_State"] # list of propreties to use as column to discribe the output
parameters=["Number_pulse","QWP","Initial_State"]# list of propreties to use as column to discribe the output
# retrive propreties to fill the columns and append the fit parameters
np.savetxt(os.path.join(saveDir,'GaussFit.csv'),data,header=paramName+'\n'+units,fmt='%s',delimiter=",")# export to .csv as str array and with the constructed header