catm-python-lib
Loading...
Searching...
No Matches
mcaanalysis.py
Go to the documentation of this file.
1"""!
2@file mcaanalysis.py
3@version 1
4@author Fumitaka ENDO
5@date 2025-01-30T10:52:52+09:00
6@brief analysis utilities related to MCA (Kromek)
7"""
8import sys
9import os
10
11sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
12
13import catmlib.util as util
14import plotly.express as px
15import pandas as pd
16import numpy as np
17from scipy.ndimage import gaussian_filter
18from iminuit import Minuit
19from iminuit.cost import LeastSquares
20import plotly.graph_objects as go
21import glob
22
24 """!
25 @class TMultiChannelAnalyzer
26 @brief analysis package for analysing data acquired by MCA
27 """
28
29 def __init__(self):
30 """!
31 @brief initialze object
32 @param Qmeas calculated measured charge
33 @param Qmeas_err calculated error of measured charge
34 @param dEdX stopping power in certain point
35 @param dL effective pad length
36 @param W elecrton-ion piar creation energy
37 @param Qe elementary charge
38 @param G calculated gain
39 @param G_err calculated error of gain
40 @param E calculated theoretical energy deposit in pad
41 @param a 1-st order term of calibration parameter
42 @param b 0-th order term of calibration parameter
43 @param calib_path calibration data path
44 @param datas gain measurement datas
45 @param input_values gain measurement datas path
46 @param calib_fit_xs data list for the data point x near each peak
47 @param calib_fit_ys data list for the gausian value near each peak
48 @param calib_means mean values list from fitting result by iminuit
49 @param calib_sigmas sigma values list from fitting result by iminuit
50 @param data_files list of data path
51 @param data_files_name list of data path name
52 @param datas_x lits of mca data points (x)
53 @param datas_y lits of mca data points (y)
54 @param datas_h lits of mca data points (frequency)
55 @param datas_peak_count list of found peak count
56 @param datas_peak_indices list of found peak index
57 @param datas_fit_xs list of data point x picked up mca data around peak
58 @param datas_fit_ys list of gausian values picked up mca data around peak
59 @param datas_fit_oys list of data point y picked up mca data around peak
60 @param datas_means list of fitted mean value
61 @param datas_sigmas list of fitted sigma value
62 @param voltages input voltage values
63 @return None
64 """
65 self.Qmeas = []
66 self.Qmeas_err = []
67 self.dEdX = 1e6 # [eV/mm]
68 self.dL = 12 # [mm]
69 self.W = 26 # [eV]
70 self.Qe = 1.602e-7 # [pC]
71 self.G = []
72 self.G_err = []
73 self.E = 1
74 self.a = 1
75 self.b = 0
76 self.calib_path = None
77 self.datas = []
78 self.input_values = []
79 self.calib_fit_xs = []
80 self.calib_fit_ys =[]
81 self.calib_means = []
82 self.calib_sigmas = []
83 self.data_files = []
84 self.data_files_name = []
85 self.datas_x = []
86 self.datas_y = []
87 self.datas_h = []
88 self.datas_peak_count = []
89 self.datas_peak_indices = []
90 self.datas_fit_xs = []
91 self.datas_fit_ys = []
92 self.datas_fit_oys = []
93 self.datas_means = []
94 self.datas_sigmas = []
95 self.voltages = []
96
97 def get_Qmeas(self):
98 """!
99 @brief get calculated measured charge and error
100 @return Qmeas and error of Qmeas ([float, float, ... , float], [float, float, ... , float])
101 """
102 return self.Qmeas, self.Qmeas_err
103
104 def get_Gain(self):
105 """!
106 @brief get calculated gain and error
107 @return Gain and error of Gain ([float, float, ... , float], [float, float, ... , float])
108 """
109 return self.G, self.G_err
110
111 def set_voltages(self, val):
112 """!
113 @brief set list of voltage value
114 @param val reference values [float, float, ... , float]
115 @return None
116 """
117 self.voltages = val
118
119 def set_dEdX(self, val):
120 """!
121 @brief set energy loss in small interval at certain point
122 @param val reference values
123 @return None
124 """
125 self.dEdX = val
126
127 def set_dL(self, val):
128 """!
129 @brief set effective pad length
130 @param val reference values
131 @return None
132 """
133 self.dL = val
134
135 def set_W(self, val):
136 """!
137 @brief set electron-ion pair cratrion energy
138 @param val reference values
139 @return None
140 """
141 self.W = val
142
143 def calculate_Qmeas(self):
144 """!
145 @brief calculate measured charge
146
147 More details:
148 calculation result is stored to `self.Qmeas` and `self.Qmeas_err`
149
150 @return None
151 """
152 if len(self.datas_means)*len(self.datas_sigmas)>0:
153 self.Qmeas = self.linear( np.array(self.datas_means).flatten(), self.b, self.a)
154 self.Qmeas_err = self.a * np.array(self.datas_sigmas).flatten()
155 else:
156 print('measured values are empty.')
157
158 def calculate_Gain(self):
159 """!
160 @brief calculate gain
161
162 More details:
163 calculation result is stored to self.G and self.G_err
164
165 @return None
166 """
167 if len(self.Qmeas)*len(self.Qmeas_err)>0:
168 self.G = self.Qmeas / ( self.dEdX * self.dL * self.Qe / self.W )
169 self.G_err = self.Qmeas_err / ( self.dEdX * self.dL * self.Qe / self.W )
170 else:
171 print('measured values are empty.')
172
173 def gaussian(self, x, amplitude, mean, sigma):
174 """!
175 @brief calculate gaussian value
176 @param amplitude constant
177 @param mean mean value
178 @param sigma sigma value
179 @return gaus(amplitude, mean, sigma)
180 """
181 return amplitude * np.exp(-0.5 * ((x - mean) / sigma) ** 2)
182
183 def linear(self, x, p0, p1):
184 """!
185 @brief calculate liner transform value
186 @param x reference values
187 @param p0 coefficient of 0-th order term
188 @param p1 coefficient of 1-st order term
189 @return gaus(amplitude, mean, sigma)
190 """
191 return p0 + p1 * x
192
193 def set_calibration_file_path(self, file_path):
194 """!
195 @brief set path of calibration data
196 @param file_path file path for mca data
197 @return None
198 """
199 self.calib_path = file_path
200
201 def set_input_values(self, values):
202 """!
203 @brief set path of measured data
204 @param file_path file path for mca data
205 @return None
206 """
207 self.input_values = values
208
209 def find_peak(self, smooth_parameter=3, input_label='calib' ,debug=False):
210 """!
211 @brief search peaks for calibraion data
212
213 More details:
214 should be set before executinf this function.
215 result id stored to, self.calib_data_peak_count and self.calib_data_peak_indices for calibration, self.datas_peak_count, self.datas_peak_indices for data
216
217 @param smooth_parameter smear parameter for original histogram
218 @param input_label label for data (calibration:'calib', data list:'data')
219 @param debug debug flag. if true, dump peak finding result (default:False)
220 @return None
221 """
222 if input_label == 'calib':
223 self.calib_data_x, self.calib_data_y = util.dataforming.read_spe_file(self.calib_path)
224 self.calib_data_h = util.dataforming.create_histogram_data_from_points(self.calib_data_x, self.calib_data_y)
225 smoothed_data = gaussian_filter(self.calib_data_y, smooth_parameter)
226 self.calib_data_peak_count, self.calib_data_peak_indices = util.dataforming.find_peaks(smoothed_data)
227
228 if debug:
229 print("(calibration) number of peak:", self.calib_data_peak_count,", index of peak:", self.calib_data_peak_indices)
230
231 else:
232 for i in range(len(self.data_files)):
233
234 file_name = os.path.basename(self.data_files[i])
235 data_x, data_y = util.dataforming.read_spe_file(self.data_files[i])
236 data_h = util.dataforming.create_histogram_data_from_points(data_x, data_y)
237 smoothed_data = gaussian_filter(data_y, smooth_parameter)
238 data_peak_count, data_peak_indices = util.dataforming.find_peaks(smoothed_data)
239
240 self.datas_x.append(data_x)
241 self.datas_y.append(data_y)
242 self.datas_h.append(data_h)
243 self.datas_peak_count.append(data_peak_count)
244 self.datas_peak_indices.append(data_peak_indices)
245 self.data_files_name.append(os.path.splitext(file_name)[0])
246
247 if debug:
248 print("(data) :", i, " number of peak:", data_peak_count, ", index of peak:", data_peak_indices)
249
250 def fit_calibration_data(self, width=30, initial_sigma=10, plot_flag=False):
251 """!
252 @brief peak fitting for calibration data
253
254 More details:
255 fitting result is stored to `self.calib_fit_xs`, `self.calib_fit_ys`, `self.calib_means`, `self.calib_sigmas`
256
257 @param width fitting range from center position (`self.calib_data_x[self.calib_data_peak_indices[i]]`)
258 @param initial_sigma initial value for using iminuit fitting
259 @param plot_flag plot flag. if true, draw data by plotly (default:False)
260 @return None
261 """
262 for i in range(len(self.calib_data_peak_indices)):
263 xarr = np.array(self.calib_data_x)
264 yarr = np.array(self.calib_data_y)
265
266 fit_min, fit_max = self.calib_data_x[self.calib_data_peak_indices[i]] - width, self.calib_data_x[self.calib_data_peak_indices[i]] + width
267 mask = (xarr >= fit_min) & (xarr <= fit_max)
268 xarr_mask = xarr[mask]
269 yarr_mask = yarr[mask]
270
271
272 least_squares = LeastSquares(xarr_mask, yarr_mask, np.ones_like(yarr_mask), self.gaussian)
273 m = Minuit(least_squares, amplitude=self.calib_data_y[self.calib_data_peak_indices[i]], mean=self.calib_data_x[self.calib_data_peak_indices[i]], sigma=initial_sigma)
274 m.errordef = 1
275 m.migrad()
276
277 self.calib_fit_xs.append(xarr_mask)
278 self.calib_fit_ys.append(self.gaussian(xarr_mask, m.values['amplitude'], m.values['mean'], m.values['sigma']))
279
280 self.calib_means.append(m.values['mean'])
281 self.calib_sigmas.append(m.values['sigma'])
282
283 if plot_flag:
284 categories = ["mca"] * len(self.calib_data_h)
285 df = pd.DataFrame({'Value': self.calib_data_h, 'Category': categories})
286 fig = px.histogram(df, x='Value', color='Category', nbins=1023, labels={'Value': 'Channel'}, title='MCA Histogram')
287
288 for i in range(len(self.calib_means)):
289 fig.add_trace(
290 go.Scatter(
291 x=self.calib_fit_xs[i], y=self.calib_fit_ys[i], mode='lines',
292 name=f"FID:{i}, ({self.calib_means[i]:3.2f}, {self.calib_sigmas[i]:3.2f})",
293 line=dict(color='red', width=1.5), opacity=0.5))
294
295 fig.show()
296
297 def calculate_calibration_parameters(self, plot_flag=False):
298 """!
299 @brief calculate calibration paramters
300
301 More details:
302 calibraion paramter is determined by iminuit fitting.
303
304 his function sould be executed after executing `self.fit_calibration_data()`.
305 fitting result is stored to `self.a`(1st), `self.b`(0th)
306
307 @param plot_flag plot flag. if true, draw data by plotly (default:False)
308 @return None
309 """
310 if len(self.calib_sigmas) == len(self.input_values):
311 self.calib_means_err = []
312 for i in range(len(self.calib_means)):
313 self.calib_means_err.append(self.calib_sigmas[i]/self.calib_means[i])
314
315 least_squares = LeastSquares(self.input_values, self.calib_means, self.calib_means_err, self.linear)
316 m = Minuit(least_squares, p0=0, p1=100)
317 m.errordef = 1
318 m.migrad()
319 params = m.values
320
321 self.b = -params['p0']/params['p1']
322 self.a = 1/params['p1']
323
324 if plot_flag:
325 fig = go.Figure(
326 data = go.Scatter(
327 y=self.input_values, x=self.calib_means,
328 error_x=dict(type='data', array=self.calib_means_err, thickness=2, width=5, visible=True),
329 mode='markers', name='fitted data plot'))
330 fig.update_layout(title='Input Values vs Fitted Values', yaxis_title='Input values', xaxis_title='Fitted value', title_x=0.05, title_font=dict(size=20))
331 fig.add_trace(
332 go.Scatter(
333 x=np.array(self.calib_means), y=self.linear(np.array(self.calib_means), self.b , self.a), mode='lines',
334 name=f"{self.b:.3f}+{self.a:.3f}x",
335 line=dict(color='red', width=2), opacity=0.5))
336 fig.show()
337 else:
338 print('peak number does not match input list.')
339 print('calibration parameter calculation process wad skipped.')
340
341 def remove_fitted_peak(self, list_label='calib', index_list=[]):
342 """!
343 @brief remove specific peak
344 @param list_label label for data (calibration:'calib', data list:'data')
345 @param index_list list of index to be deleted
346 @return None
347 """
348 if list_label == 'calib':
349 for index in sorted(index_list, reverse=True):
350 if 0 <= index < len(self.calib_sigmas):
351 del self.calib_sigmas[index]
352
353 for index in sorted(index_list, reverse=True):
354 if 0 <= index < len(self.calib_means):
355 del self.calib_means[index]
356 else:
357 for i in range(len(self.datas_means)):
358 for index in sorted(index_list[i], reverse=True):
359 if 0 <= index < len(self.datas_fit_xs[i]):
360 del self.datas_fit_xs[i][index]
361
362 for index in sorted(index_list[i], reverse=True):
363 if 0 <= index < len(self.datas_fit_ys[i]):
364 del self.datas_fit_ys[i][index]
365
366 for index in sorted(index_list[i], reverse=True):
367 if 0 <= index < len(self.datas_fit_oys[i]):
368 del self.datas_fit_oys[i][index]
369
370 for index in sorted(index_list[i], reverse=True):
371 if 0 <= index < len(self.datas_means[i]):
372 del self.datas_means[i][index]
373
374 for index in sorted(index_list[i], reverse=True):
375 if 0 <= index < len(self.datas_sigmas[i]):
376 del self.datas_sigmas[i][index]
377
378 def get_data_file_path_list(self, filepath='./*spe'):
379 """!
380 @brief sort and set data file list
381
382 More details:
383 can use wildcard with input path
384
385 @param filepath file path
386 @return None
387 """
388 self.data_files = sorted(glob.glob(filepath))
389
390 def add_data_file_path_list(self, filepath='./*spe'):
391 """!
392 @brief sort and add data file list
393
394 More details:
395 can use wildcard with input path
396
397 @param filepath file path
398 @return None
399 """
400 add_files = sorted(glob.glob(filepath))
401 for i in range(len(add_files)):
402 self.data_files.append(add_files[i])
403
404 def check_data_file(self):
405 """!
406 @brief dump load file list
407 @return None
408 """
409 for i in range(len(self.data_files)):
410 print(self.data_files[i])
411
412
413 def fit_data(self, width=30, initial_sigma=10):
414 """!
415 @brief peak fitting for list of mca data
416
417 More details:
418 fitting result is stored to `self.datas_fit_xs`, `self.datas_fit_ys`, `self.datas_fit_oys`, `self.datas_means`, `self.datas_sigmas`
419
420 @param width fitting range from center position (`self.calib_data_x[self.calib_data_peak_indices[i]]`)
421 @param initial_sigma initial value for using iminuit fitting
422 @return None
423 """
424 for j in range(len(self.datas_x)):
425
426 data_fit_xs = []
427 data_fit_ys = []
428 data_fit_oys = []
429 data_means = []
430 data_sigmas = []
431
432 for i in range(len(self.datas_peak_indices[j])):
433 xarr = np.array(self.datas_x[j])
434 yarr = np.array(self.datas_y[j])
435
436 fit_min, fit_max = self.datas_x[j][self.datas_peak_indices[j][i]] - width, self.datas_x[j][self.datas_peak_indices[j][i]] + width
437 mask = (xarr >= fit_min) & (xarr <= fit_max)
438 xarr_mask = xarr[mask]
439 yarr_mask = yarr[mask]
440
441 least_squares = LeastSquares(xarr_mask, yarr_mask, np.ones_like(yarr_mask), self.gaussian)
442 m = Minuit(least_squares, amplitude=self.datas_y[j][self.datas_peak_indices[j][i]], mean=self.datas_x[j][self.datas_peak_indices[j][i]], sigma=initial_sigma)
443 m.errordef = 1
444 m.migrad()
445
446 data_fit_xs.append(xarr_mask)
447 data_fit_ys.append(self.gaussian(xarr_mask, m.values['amplitude'], m.values['mean'], m.values['sigma']))
448 data_fit_oys.append(yarr_mask)
449 data_means.append(m.values['mean'])
450 data_sigmas.append(m.values['sigma'])
451
452 self.datas_fit_xs.append(data_fit_xs)
453 self.datas_fit_ys.append(data_fit_ys)
454 self.datas_fit_oys.append(data_fit_oys)
455 self.datas_means.append(data_means)
456 self.datas_sigmas.append(data_sigmas)
457
458 def draw_fitted_datas(self):
459 """!
460 @brief draw fitting result for list of mca data
461 @return None
462 """
463 k = 0
464
465 for i in range(len(self.datas_fit_xs)):
466 for j in range(len(self.datas_fit_xs[i])):
467
468 data_h = util.dataforming.create_histogram_data_from_points(self.datas_fit_xs[i][j], self.datas_fit_oys[i][j])
469
470 if k == 0:
471 categories = [f"{self.data_files_name[i]}, FID:{i}, HID:{j}"] * len(data_h)
472 histograms = data_h
473
474 else:
475 categories = categories + [f"{self.data_files_name[i]}, FID:{i}, HID:{j}"] * len(data_h)
476 histograms = histograms + data_h
477
478 k = k + 1
479
480 df = pd.DataFrame({'Value': histograms, 'Category': categories})
481 fig = px.histogram(df, x="Value", color="Category", nbins=1023, opacity=0.75)
482
483 for i in range(len(self.datas_fit_xs)):
484 for j in range(len(self.datas_fit_xs[i])):
485 fig.add_trace(
486 go.Scatter(
487 x=self.datas_fit_xs[i][j], y=self.datas_fit_ys[i][j], mode='lines',
488 name=f"FID:{i}, HID:{j}, ({self.calib_means[i]:3.2f}, {self.calib_sigmas[i]:3.2f})",
489 line=dict(color='black', width=1.5), opacity=0.5))
490
491 fig.show()
492
493 def draw_gain_curve(self):
494 """!
495 @brief draw fitting result for list of mca data
496
497 More details:
498 this function should be executed after executing `self.draw_fitted_datas()`, `self.calculate_Qmeas()`, `self.calculate_Gain()`
499
500 @return None
501 """
502 fig = go.Figure(
503 data = go.Scatter(
504 x=self.voltages, y=self.G,
505 error_y=dict(type='data', array=self.G_err, thickness=2, width=5, visible=True),
506 name='fitted data plot'))
507 fig.update_layout(title='Gain Curve', yaxis_title='Gain', xaxis_title='Voltage [V]', title_x=0.05, title_font=dict(size=20))
508
509 fig.show()
analysis package for analysing data acquired by MCA