deconvolution.py ダウンロード/コピー
deconvolution.py
deconvolution.py
1import sys
2import os.path
3import argparse
4import csv
5import re
6from math import exp, log, sqrt
7import numpy as np
8import pandas as pd
9import scipy.signal
10import scipy.optimize
11import matplotlib.pyplot as plt
12import matplotlib.widgets as wg
13
14
15from tklib.tkutils import replace_path, getarg, getintarg, getfloatarg, pint, pfloat
16from tklib.tksci.tksci import kB, e
17from tklib.tkvariousdata import tkVariousData
18from tklib.tkapplication import tkApplication
19from tklib.tkgraphic.tkplotevent import tkPlotEvent
20
21
22"""
23概要:
24 スペクトルデータの逆畳み込みを実行するスクリプト。
25詳細説明:
26 このスクリプトは、1次元スペクトルデータに対してガウス関数またはローレンツ関数を
27 用いた逆畳み込み処理を提供します。Jacobi法、Gauss-Seidel法、Smoothing penalty法、
28 Ridge回帰、非負最小二乗法(NNLS)、FFT法、scipy.signal.deconvolve
29 など、複数の逆畳み込みアルゴリズムをサポートしています。
30
31主な機能:
32 Jacobi法による反復逆畳み込み、Gauss-Seidel法による反復逆畳み込み、
33 Smoothing penalty法による正則化逆畳み込み、Ridge回帰による正則化逆畳み込み、
34 非負最小二乗法(NNLS)による制約付き逆畳み込み、FFT(高速フーリエ変換)を用いた逆畳み込み、
35 scipy.signal.deconvolve を使用した直接逆畳み込み、
36 numpy.convolve を用いた畳み込みによる動作確認、入力データの平滑化、拡張、整形オプション。
37
38注意点:
39 畳み込み関数を規格化するため、フィルター関数(ywf)の和で割る必要があります。
40 numpy.convolve の戻り値はフィルター関数の点数の約1/2だけ前後に拡張されます。
41 mode='same' を指定すると、戻り値の範囲は入力関数の範囲に一致します。
42 逆畳み込みで元のデータに戻すには、拡張部分のデータも必要となる場合があります。
43 scipy.signal.deconvolve は入力データの質に非常に敏感です。以下の条件を満足しないと、
44 期待する結果が得られない場合があります。フィルター関数の幅は入力データよりも短いこと。
45 フィルター関数の値がある程度の値よりも大きいこと(0に近い値があると問題になる可能性)。
46 入力データの前には、フィルター関数の幅よりも大きいゼロ領域が必要な場合があること。
47 入力データにノイズがある場合は、適切に平滑化処理が必要となること。
48
49参考文献:
50 「科学計測のための波形データ処理」、南茂夫 編著 (CQ出版社, 1986)
51関連リンク:
52 deconvolution_usage
53"""
54
55
56#===================
57# Global parametrs
58#===================
59app = tkApplication()
60
61pi = 3.1415926535
62
63# Input file
64infile = "pes.csv"
65template = "StandardGraph.xlsm"
66
67
68# Analysis range
69xlabel = 0
70ylabel = 1
71xmin = -1.0e8 # -4.5 # eV
72xmax = 1.0e8 # 2.0 # eV
73
74# mode = [plot|gs|jacobi|sp|ridge|nnls|convolve|fft|deconvolve]
75mode = 'gs'
76
77# Filter parameters for window function
78func_type = 'gauss'
79Wa = 0.12 # eV
80Grange = 2.0
81
82# for update graph
83sleeptime = 0.05
84
85# for Ridge regression
86alpha = 0.1
87
88# for NNLS / non-negative regularized deconvolution
89nnls_maxiter = None
90nnls_penalty = 'ridge'
91
92# for Jacobi/Gauss-Seidel method
93dump = 1.0
94nmaxiter = 300
95eps = 1.0e-4
96nsmooth = 5
97
98zero_correction = 0
99
100# only for 'convolve': convmode = [same|full|]
101convmode = 'same'
102# smoothmode = [convolve|extend|average]
103smoothmode = 'convolve+extend'
104
105# only for 'convolve' and 'fft': 'exnted data' parameters
106kzero = 5
107klin = 5
108
109figsize = (6, 6)
110
111# x-axis sign conversion. Useful for spectra stored as binding energy
112# when plotting/analyzing on the opposite energy axis.
113flip_x = 0
114
115#===================
116# 起動時引数
117#===================
118scriptname = sys.argv[0]
119
120
121def parse_xlimit(value):
122 """
123 概要:
124 x軸の範囲制限を解析します。
125 詳細説明:
126 '*'または'none'は無制限を示し、数値文字列は浮動小数点数に変換されます。
127 これは、xmin/xmaxがオープンに設定できる以前の動作を維持します。
128 引数:
129 :param value: 解析する値。数値、文字列、またはNone。
130 :type value: any
131 戻り値:
132 :returns: 解析されたx軸の制限値、またはNone。
133 :rtype: float or None
134 """
135 if value is None:
136 return None
137 if isinstance(value, (int, float)):
138 return float(value)
139 text = str(value).strip()
140 if text == '' or text == '*' or text.lower() in ('none', 'null'):
141 return None
142 return float(text)
143
144
145def parse_label(value):
146 """
147 概要:
148 データ列セレクタを解析します。
149 詳細説明:
150 整数インデックスまたはラベル文字列として解析します。
151 引数:
152 :param value: 解析する値。整数または文字列。
153 :type value: int or str
154 戻り値:
155 :returns: 解析された列インデックス(整数)またはラベル(文字列)。
156 :rtype: int or str
157 """
158 if isinstance(value, int):
159 return value
160 text = str(value).strip()
161 try:
162 return int(text)
163 except ValueError:
164 return text
165
166
167def build_arg_parser():
168 """
169 概要:
170 コマンドライン引数パーサーを構築します。
171 詳細説明:
172 1次元スペクトルデータをガウス関数またはローレンツ関数型の装置関数で
173 逆畳み込みするための引数パーサーを作成します。
174 戻り値:
175 :returns: 設定済みのArgument Parserオブジェクト。
176 :rtype: argparse.ArgumentParser
177 """
178 parser = argparse.ArgumentParser(
179 description=(
180 "Deconvolute one-dimensional spectral data with a Gaussian or "
181 "Lorentzian instrumental function."
182 ),
183 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
184 )
185
186 parser.add_argument(
187 'infile',
188 nargs='?',
189 default=infile,
190 help='Input data file readable by tkVariousData.',
191 )
192 parser.add_argument(
193 '--mode',
194 choices=['plot', 'gs', 'jacobi', 'sp', 'ridge', 'nnls', 'convolve', 'fft', 'deconvolve'],
195 default=mode,
196 help=(
197 'Calculation mode. nnls is non-negative least squares using the '
198 'instrumental function as basis functions.'
199 ),
200 )
201 parser.add_argument('--xmin', type=parse_xlimit, default=xmin,
202 help="Minimum x value. Use '*' or 'none' for no lower limit.")
203 parser.add_argument('--xmax', type=parse_xlimit, default=xmax,
204 help="Maximum x value. Use '*' or 'none' for no upper limit.")
205 parser.add_argument('--xlabel', type=parse_label, default=xlabel,
206 help='X column index or label.')
207 parser.add_argument('--ylabel', type=parse_label, default=ylabel,
208 help='Y column index or label.')
209 parser.add_argument('--flip-x', '--flip_x', dest='flip_x', type=int, choices=[0, 1], default=flip_x,
210 help='If 1, multiply input x values by -1 before range filtering, plotting, and calculation.')
211
212 parser.add_argument('--func-type', choices=['gauss', 'lorentz', 'lorentzian'],
213 default=func_type,
214 help='Instrumental/window-function type.')
215 parser.add_argument('--wa', type=float, default=Wa,
216 help='Half width at half maximum of the instrumental function.')
217 parser.add_argument('--grange', type=float, default=Grange,
218 help='Window-function range in units of wa for sampled filters.')
219
220 parser.add_argument('--alpha', type=float, default=alpha,
221 help='Regularization coefficient for ridge/sp modes.')
222 parser.add_argument('--dump', type=float, default=dump,
223 help='Damping factor for Jacobi/Gauss-Seidel iterations.')
224 parser.add_argument('--nmaxiter', type=int, default=nmaxiter,
225 help='Maximum iteration count for Jacobi/Gauss-Seidel modes.')
226 parser.add_argument('--eps', type=float, default=eps,
227 help='Relative convergence criterion for iterative modes.')
228 parser.add_argument('--nsmooth', type=int, default=nsmooth,
229 help='Smoothing width used by polynomial smoothing.')
230 parser.add_argument('--zero-correction', type=int, choices=[0, 1], default=zero_correction,
231 help='Clip negative values to zero after each iterative update.')
232
233 parser.add_argument('--convmode', choices=['same', 'full', 'valid'], default=convmode,
234 help='np.convolve mode used when smoothmode contains convolve.')
235 parser.add_argument('--smoothmode', default=None,
236 help=(
237 "Combination of 'average', 'extend', and 'convolve'. "
238 "Use 'none' or an empty string for no preprocessing. "
239 "Default is mode-dependent: 'convolve+extend' for "
240 "convolve/fft, otherwise no preprocessing."
241 ))
242 parser.add_argument('--kzero', type=int, default=kzero,
243 help='Number of half-window widths used for zero extension.')
244 parser.add_argument('--klin', type=int, default=klin,
245 help='Number of half-window widths used for linear edge shaping.')
246
247 parser.add_argument('--nnls-maxiter', type=int, default=nnls_maxiter,
248 help='Maximum iteration count for nnls / bounded least-squares solvers.')
249 parser.add_argument('--nnls-penalty', choices=['ridge', 'smooth'], default=nnls_penalty,
250 help=(
251 'Regularization penalty used when --mode nnls and --alpha > 0. '
252 'ridge penalizes coefficient size; smooth penalizes first differences. '
253 'Use --alpha 0 for pure NNLS without regularization.'
254 ))
255 parser.add_argument('--template', default=template,
256 help='Excel template used by tkVariousData.to_excel.')
257 parser.add_argument('--figsize', type=float, nargs=2, default=figsize,
258 metavar=('WIDTH', 'HEIGHT'),
259 help='Matplotlib figure size.')
260 return parser
261
262
263def usage(app=None):
264 """
265 概要:
266 スクリプトの使用方法を表示します。
267 詳細説明:
268 build_arg_parser() によって生成されたヘルプメッセージを出力します。
269 引数:
270 :param app: Tkinterアプリケーションオブジェクト。現在は使用されません。
271 :type app: tkApplication or None
272 """
273 print(build_arg_parser().format_help())
274
275
276def apply_args_to_globals(args):
277 """
278 概要:
279 解析されたコマンドライン引数をグローバル変数に適用します。
280 詳細説明:
281 argsオブジェクトから取得した値で、スクリプト全体の動作を制御するグローバル変数を更新します。
282 引数:
283 :param args: コマンドライン引数を格納したNamespaceオブジェクト。
284 :type args: argparse.Namespace
285 """
286 global infile, template, xlabel, ylabel, xmin, xmax, mode, flip_x
287 global func_type, Wa, Grange, sleeptime, alpha, nnls_maxiter, nnls_penalty
288 global dump, nmaxiter, eps, nsmooth, zero_correction
289 global convmode, smoothmode, kzero, klin, figsize
290
291 infile = args.infile
292 template = args.template
293 xlabel = args.xlabel
294 ylabel = args.ylabel
295 xmin = args.xmin
296 xmax = args.xmax
297 mode = args.mode
298 flip_x = args.flip_x
299
300 func_type = 'lorentz' if args.func_type == 'lorentzian' else args.func_type
301 Wa = args.wa
302 Grange = args.grange
303 alpha = args.alpha
304 nnls_maxiter = args.nnls_maxiter
305 nnls_penalty = args.nnls_penalty
306
307 dump = args.dump
308 nmaxiter = args.nmaxiter
309 eps = args.eps
310 nsmooth = args.nsmooth
311 zero_correction = args.zero_correction
312
313 convmode = args.convmode
314 if args.smoothmode is None:
315 smoothmode = 'convolve+extend' if mode in ('convolve', 'fft') else ''
316 elif args.smoothmode.lower() == 'none':
317 smoothmode = ''
318 else:
319 smoothmode = args.smoothmode
320
321 kzero = args.kzero
322 klin = args.klin
323 figsize = tuple(args.figsize)
324
325
326#header, ext = os.path.splitext(infile)
327#outfile = header + '-deconvoluted.xlsx'
328
329def Gaussian(x, x0, whalf):
330 """
331 概要:
332 ガウス関数を計算します。
333 詳細説明:
334 中心 x0、半値半幅 whalf のガウス関数を返します。
335 引数:
336 :param x: 評価点。
337 :type x: float
338 :param x0: 関数の中心。
339 :type x0: float
340 :param whalf: 半値半幅(HWHM)。
341 :type whalf: float
342 戻り値:
343 :returns: ガウス関数の値。
344 :rtype: float
345 """
346#A = 1/whalf * sqrt(ln2 / pi)
347 A = 0.469718639 / whalf
348#a = whalf / sqrt(ln2)
349 a = whalf / 0.832554611
350 X = (x - x0) / a
351 return A * exp(-X*X)
352
353def Lorentzian(x, x0, whalf):
354 """
355 概要:
356 ローレンツ関数を計算します。
357 詳細説明:
358 中心 x0、半値半幅 whalf のローレンツ関数を返します。
359 引数:
360 :param x: 評価点。
361 :type x: float
362 :param x0: 関数の中心。
363 :type x0: float
364 :param whalf: 半値半幅(HWHM)。
365 :type whalf: float
366 戻り値:
367 :returns: ローレンツ関数の値。
368 :rtype: float
369 """
370#A = 1/whalf/pi
371 A = 1.0 / whalf / pi
372 X = (x - x0) / whalf
373 return A / (1.0 + X * X)
374
375def convolve_func(x, x0, whalf=None):
376 """
377 概要:
378 設定された関数タイプに基づいて畳み込み関数を計算します。
379 詳細説明:
380 func_type グローバル変数に応じてガウス関数またはローレンツ関数を呼び出します。
381 whalf が指定されない場合は、グローバル変数 Wa が使用されます。
382 引数:
383 :param x: 評価点。
384 :type x: float
385 :param x0: 関数の中心。
386 :type x0: float
387 :param whalf: 半値半幅(HWHM)。指定されない場合はグローバルなWaが使用されます。
388 :type whalf: float or None
389 戻り値:
390 :returns: 畳み込み関数の値。
391 :rtype: float
392 """
393 if whalf is None:
394 whalf = Wa
395 if func_type == 'gauss':
396 return Gaussian(x, x0, whalf)
397 else:
398 return Lorentzian(x, x0, whalf)
399
400def Hij(xstep, Wa, Grange, i, j):
401 """
402 概要:
403 畳み込み行列の要素 Hij を計算します。
404 詳細説明:
405 ステップサイズ xstep、半値半幅 Wa、範囲 Grange を用いて、
406 インデックス i と j に対応する畳み込み関数の値を計算します。
407 引数:
408 :param xstep: x軸のステップサイズ。
409 :type xstep: float
410 :param Wa: 半値半幅(HWHM)。
411 :type Wa: float
412 :param Grange: 畳み込み関数の範囲を Wa の単位で指定。
413 :type Grange: float
414 :param i: 行インデックス。
415 :type i: int
416 :param j: 列インデックス。
417 :type j: int
418 戻り値:
419 :returns: Hij 行列の要素の値。
420 :rtype: float
421 """
422 return convolve_func(xstep * i, xstep * j, Wa)
423
424def Ridge(x, y, m, lsqfunc, w, nGmax, alpha = 0.0, is_print = False):
425 """
426 概要:
427 Ridge回帰により係数を計算します。
428 詳細説明:
429 最小二乗関数 lsqfunc とエッジ補正重み w を用いて、
430 観測データ y から係数 ci を推定します。正則化パラメータ alpha を使用します。
431 引数:
432 :param x: x軸のデータ点配列。
433 :type x: numpy.ndarray or list
434 :param y: 観測データ配列。
435 :type y: numpy.ndarray or list
436 :param m: 係数の数(データ点の数)。
437 :type m: int
438 :param lsqfunc: 最小二乗法で使用する関数。2つの引数 (x1, x2) を取ります。
439 :type lsqfunc: callable
440 :param w: エッジ補正重み配列。
441 :type w: list
442 :param nGmax: 畳み込み関数の最大インデックス範囲。
443 :type nGmax: int
444 :param alpha: 正則化パラメータ(リッジパラメータ)。デフォルトは0.0。
445 :type alpha: float
446 :param is_print: 中間結果を出力するかどうかのフラグ。デフォルトはFalse。
447 :type is_print: bool
448 戻り値:
449 :returns: 推定された係数のリスト。
450 :rtype: list
451 """
452 n = len(x)
453 Si = np.empty([m, 1])
454 Sij = np.empty([m, m])
455
456 def cal_w(j):
457 if j <= nGmax:
458 wj = w[j]
459 elif n - 1 - j < nGmax:
460 wj = w[n-1-j]
461 else:
462 wj = w[nGmax - 1]
463 return wj
464
465 for l in range(0, m):
466 v = 0.0
467 for i in range(0, n):
468 v += y[i] * lsqfunc(x[l], x[i])
469# Si[l, 0] = v
470 Si[l, 0] = v / cal_w(l)
471
472 for j in range(0, m):
473 for l in range(j, m):
474 v = 0.0
475 for i in range(0, n):
476 v += lsqfunc(x[j], x[i]) * lsqfunc(x[l], x[i])
477# Sij[j, l] = Sij[l, j] = v
478 Sij[j, l] = Sij[l, j] = v / cal_w(j) / cal_w(l)
479 Sij[j, j] += alpha
480
481 if is_print:
482 print("Vector and Matrix:")
483 print("Si=")
484 print(Si)
485 print("Sij=")
486 print(Sij)
487 print("")
488
489 ci = np.linalg.solve(Sij, Si)
490 ci = ci.transpose().tolist()
491 return ci[0]
492
493def Smoothing_Penalty_Regression(x, y, m, lsqfunc, w, nGmax, alpha = 0.0, nsmooth = 1, is_print = False):
494 """
495 概要:
496 Smoothing Penalty回帰により係数を計算します。
497 詳細説明:
498 最小二乗関数 lsqfunc とエッジ補正重み w を用いて、
499 観測データ y から係数 ci を推定します。正則化パラメータ alpha と平滑化幅 nsmooth を使用します。
500 引数:
501 :param x: x軸のデータ点配列。
502 :type x: numpy.ndarray or list
503 :param y: 観測データ配列。
504 :type y: numpy.ndarray or list
505 :param m: 係数の数(データ点の数)。
506 :type m: int
507 :param lsqfunc: 最小二乗法で使用する関数。2つの引数 (x1, x2) を取ります。
508 :type lsqfunc: callable
509 :param w: エッジ補正重み配列。
510 :type w: list
511 :param nGmax: 畳み込み関数の最大インデックス範囲。
512 :type nGmax: int
513 :param alpha: 正則化パラメータ(ペナルティパラメータ)。デフォルトは0.0。
514 :type alpha: float
515 :param nsmooth: 平滑化の幅。デフォルトは1。
516 :type nsmooth: int
517 :param is_print: 中間結果を出力するかどうかのフラグ。デフォルトはFalse。
518 :type is_print: bool
519 戻り値:
520 :returns: 推定された係数のリスト。
521 :rtype: list
522 """
523 n = len(x)
524 Si = np.empty([m, 1])
525 Sij = np.empty([m, m])
526
527 def cal_w(j):
528 if j <= nGmax:
529 wj = w[j]
530 elif n - 1 - j < nGmax:
531 wj = w[n-1-j]
532 else:
533 wj = w[nGmax - 1]
534 return max([wj, 1.0e-6])
535# return wj
536
537 for l in range(0, m):
538 v = 0.0
539 for i in range(0, n):
540 v += y[i] * lsqfunc(x[l], x[i])
541# Si[l, 0] = v
542 Si[l, 0] = v / cal_w(l)
543
544 for j in range(0, m):
545 for l in range(j, m):
546 v = 0.0
547 for i in range(0, n):
548 v += lsqfunc(x[j], x[i]) * lsqfunc(x[l], x[i])
549# Sij[j, l] = Sij[l, j] = v
550 Sij[j, l] = Sij[l, j] = v / cal_w(j) / cal_w(l)
551
552 nband = int(nsmooth / 2 + 1.0001)
553 use_ridge = 1
554 if use_ridge > 1:
555 for j in range(0, m):
556 Sij[j, j] += alpha
557 elif use_ridge > 0:
558 for j in range(0, m):
559 j0 = j - nband
560 j1 = j + nband
561 if j0 < 0:
562 j0 = 0
563 if j1 >= m:
564 j1 = m
565 for k in range(j0, j):
566 Sij[j, k] -= alpha
567 for k in range(j+1, j1):
568 Sij[j, k] -= alpha
569
570 if is_print:
571 print("Vector and Matrix:")
572 print("Si=")
573 print(Si)
574 print("Sij=")
575 print(Sij)
576 print("")
577
578 ci = np.linalg.solve(Sij, Si)
579 ci = ci.transpose().tolist()
580 return ci[0]
581
582def make_wf(Wa, Grange, xstep):
583 """
584 概要:
585 サンプリングされたウィンドウ関数(フィルター関数)を作成します。
586 詳細説明:
587 半値半幅 Wa と範囲 Grange を用いて、xstep 間隔でサンプリングされた
588 ウィンドウ関数 yG を生成し、その積算値 SG で正規化します。
589 x軸が降順であっても、ウィンドウ関数が空にならないように
590 xstep の絶対値が使用されます。
591 引数:
592 :param Wa: 半値半幅(HWHM)。
593 :type Wa: float
594 :param Grange: 畳み込み関数の範囲を Wa の単位で指定。
595 :type Grange: float
596 :param xstep: x軸のステップサイズ。
597 :type xstep: float
598 戻り値:
599 :returns: x軸のサンプリング点リストと、正規化されたウィンドウ関数値リストのタプル。
600 :rtype: tuple of (list, list)
601 例外:
602 :raises ValueError: xstepが非ゼロでない場合、または正規化積算値SGがゼロ以下の場合。
603 """
604 # The sampled window function assumes a positive grid spacing.
605 # Experimental spectra, especially XPS/HAXPES binding-energy axes,
606 # are often stored in descending x order. Use abs(xstep) here so the
607 # window does not become empty when the input axis is descending.
608 dx = abs(xstep)
609 if dx <= 0.0:
610 raise ValueError("xstep must be non-zero in make_wf().")
611
612 ixG0 = int(Grange * Wa / dx + 1.0001)
613 ixGmax = 2 * ixG0
614 nxGmax = ixG0 + 1
615 xG0 = ixG0 * dx
616
617 xG = []
618 yG = []
619 for i in range(ixGmax+1):
620 x = i * dx
621 xG.append(x)
622 yG.append(convolve_func(x, xG0, Wa))
623
624 SG = 0.0
625 for i in range(len(yG)-1):
626 SG += (yG[i] + yG[i+1]) / 2.0 * (xG[i+1] - xG[i])
627
628 if SG <= 0.0:
629 raise ValueError(
630 f"Invalid sampled window function: SG={SG}. "
631 f"Check wa={Wa}, grange={Grange}, xstep={xstep}."
632 )
633
634 for i in range(ixGmax+1):
635 yG[i] /= SG
636
637 print(" Range: {} in width".format(Grange * Wa))
638 print(" i range: {} - {} at center {}".format(0,ixGmax, xG0))
639 print(" ixGmax = ", ixGmax)
640 print(" SG = ", SG)
641
642 return xG, yG
643
644
645def convolve(xraw, yraw, ywf, **kwargs):
646 """
647 概要:
648 データを畳み込みます。
649 詳細説明:
650 numpy.convolve を使用して生データ yraw をウィンドウ関数 ywf で畳み込みます。
651 畳み込み関数の和で結果を正規化し、必要に応じてx軸を拡張します。
652 引数:
653 :param xraw: 生データのx軸配列。
654 :type xraw: numpy.ndarray or list
655 :param yraw: 生データのy軸配列。
656 :type yraw: numpy.ndarray or list
657 :param ywf: ウィンドウ関数(フィルター関数)のy値配列。
658 :type ywf: numpy.ndarray or list
659 :param kwargs: numpy.convolve に渡される追加のキーワード引数。
660 :type kwargs: dict
661 戻り値:
662 :returns: 畳み込み後のx軸配列とy軸配列のタプル。
663 :rtype: tuple of (numpy.ndarray, numpy.ndarray)
664 """
665 yconv = np.convolve(yraw, ywf, **kwargs) / sum(ywf)
666 n_new = len(yconv)
667 dn = n_new - len(yraw)
668 if dn > 0:
669 offset = int(dn / 2)
670 xmin = xraw[0]
671 xstep = xraw[1] - xmin
672 xmin_new = xmin - offset * xstep
673 print("convolve: the length of the output data has been changed "
674 + "from {} to {}".format(len(yraw), n_new))
675 print(" Add offset = {}".format(offset))
676 print(" xmin changes: {} => {}".format(xmin, xmin_new))
677 x = np.array([xmin_new + i * xstep for i in range(n_new)])
678 return x, yconv
679 return xraw, yconv
680
681def extend_smooth(x, y, nzero, nlin, xstep = 0.0):
682 """
683 概要:
684 データの端をゼロ埋めと線形補間で拡張・平滑化します。
685 詳細説明:
686 データ y の先頭に nzero 個のゼロを追加し、最初の nlin 点を線形に整形して
687 滑らかな接続を実現します。xstepは内部で計算されます。
688 引数:
689 :param x: 元のx軸配列。
690 :type x: numpy.ndarray or list
691 :param y: 元のy軸配列。
692 :type y: numpy.ndarray or list
693 :param nzero: データ先頭に追加するゼロの数。
694 :type nzero: int
695 :param nlin: 線形整形を適用するデータ点の数。
696 :type nlin: int
697 :param xstep: x軸のステップサイズ。デフォルトは0.0ですが、xから計算されます。
698 :type xstep: float
699 戻り値:
700 :returns: 拡張・平滑化されたx軸配列とy軸配列のタプル。
701 :rtype: tuple of (numpy.ndarray, numpy.ndarray)
702 """
703 xmin = x[0]
704 xstep = x[1] - x[0]
705 xmin_new = x[0] - nzero * xstep
706 n_new = nzero + len(x)
707 print("extend_smooth:")
708 print(" Add {} zeros at top of the data".format(nzero))
709 print(" xmin changes: {} => {}".format(xmin, xmin_new))
710 print(" Reshape {} input data with a linear filter".format(nlin))
711
712 xx = np.array([xmin_new + i * xstep for i in range(n_new)])
713 yy = np.zeros(n_new)
714 for i in range(nlin):
715 k = i / (nlin - 1)
716 yy[i+nzero] = k * y[i]
717 for i in range(len(x) - nlin):
718 yy[i+nzero+nlin] = y[i+nzero]
719 return xx, yy
720
721def SmoothingBySimpleAverage(y, n):
722 """
723 概要:
724 移動平均でデータを平滑化します。
725 詳細説明:
726 各データ点について、幅 n の範囲内の平均値で置き換えることでデータを平滑化します。
727 データ端では利用可能な点のみを使用します。
728 引数:
729 :param y: 平滑化するデータ配列。
730 :type y: list
731 :param n: 移動平均の窓幅。
732 :type n: int
733 戻り値:
734 :returns: 平滑化されたデータ配列。
735 :rtype: list
736 """
737 n2 = int(n / 2);
738 ndata = len(y);
739 ys = []
740 for i in range(0, ndata):
741 c = 0;
742 ys.append(0.0);
743 for k in range(i - n2, i + n2 + 1):
744 if k < 0 or k >= ndata:
745 continue
746 ys[i] += y[k]
747 c += 1
748 if c > 0:
749 ys[i] /= c;
750 else:
751 ys[i] = y[i]
752 return ys;
753
754def SmoothingByPolynomialFit(y, n):
755 """
756 概要:
757 多項式フィッティング(Savitzky-Golay風)でデータを平滑化します。
758 詳細説明:
759 各データ点について、幅 n の範囲内で多項式フィッティングを行い、
760 その中心点の値で置き換えることでデータを平滑化します。
761 Savitzky-Golayフィルターに似た重み付けが使用されます。
762 引数:
763 :param y: 平滑化するデータ配列。
764 :type y: list
765 :param n: 平滑化の窓幅。
766 :type n: int
767 戻り値:
768 :returns: 平滑化されたデータ配列。
769 :rtype: list
770 """
771 m = int(n / 2);
772 W23 = (4.0 * m * m - 1.0) * (2.0 * m + 3.0) / 3.0;
773 w23j = [0.0]*(2*m+1)
774 for j in range(-m, m+1):
775 w23j[j + m] = (3.0 * m * (m+1.0) - 1.0 - 5.0 * j * j) / W23
776
777 ndata = len(y)
778 ys = []
779 for i in range(0, ndata):
780 if(i-m < 0 or ndata <= i+m):
781 ys.append(y[i])
782 else:
783 c = 0.0;
784 ys.append(0.0);
785 for j in range(-m, m+1):
786 k = i + j
787 if k < 0 or k >= ndata:
788 continue
789 ys[i] += w23j[j+m] * y[k]
790 c += w23j[j+m]
791
792 if c > 0:
793 ys[i] /= c
794 else:
795 ys[i] = y[i]
796
797 return ys;
798
799
800def deconvolute_fft(xRaw, yRaw, xG, yG):
801 """
802 概要:
803 FFT(高速フーリエ変換)を用いて逆畳み込みを実行します。
804 詳細説明:
805 生データとウィンドウ関数をFFTし、周波数領域で除算を行うことで逆畳み込みを実行します。
806 データ長は2のべき乗に調整されます。
807 引数:
808 :param xRaw: 生データのx軸配列。
809 :type xRaw: list
810 :param yRaw: 生データのy軸配列。
811 :type yRaw: list
812 :param xG: ウィンドウ関数のx軸配列。
813 :type xG: list
814 :param yG: ウィンドウ関数のy軸配列。
815 :type yG: list
816 戻り値:
817 :returns: 逆畳み込み後のx軸配列、y軸配列、FFT処理された生データのx軸配列、y軸配列、FFT処理されたウィンドウ関数のx軸配列、y軸配列のタプル。
818 :rtype: tuple of (list, list, list, list, list, list)
819 """
820 k = sum(yG)
821
822 print("Deconvolution by FFT")
823 n = len(xRaw)
824 nlog = int(log(n) / log(2) + 1.0 - 1.0e-5)
825 nfft = pow(2, nlog)
826 print(" Data number is changed from {} to 2^{} = {} for FFT".format(n, nlog, nfft))
827
828 xmin = xRaw[0]
829 xstep = xRaw[1] - xmin
830 xRawFFT = [xmin + i * xstep for i in range(nfft)]
831 yRawFFT = np.insert(yRaw, len(yRaw), np.zeros(nfft - n))
832# filterの中心位置の原点からのずれによって、iFFT後の原点がずれる
833 yGFFT = np.insert(yG, len(yG), np.zeros(nfft - len(yG)))
834 xminG = xmin + len(xG) / 2 * xstep
835 print(" xmin: ", xmin, xminG)
836 xGFFT = [xminG + i * xstep for i in range(nfft)]
837# nadd = int((nfft - len(yG)) / 2)
838# yGFFT = np.insert(yG, len(yG), np.zeros(nadd))
839# yGFFT = np.insert(yGFFT, 0, np.zeros(nfft - len(yGFFT)))
840
841 yRawFFTed = np.fft.fft(yRawFFT)
842 yGFFTed = np.fft.fft(yGFFT)
843 ycFFTed = yRawFFTed / yGFFTed
844 ydeconv = np.fft.ifft(ycFFTed)
845 ydeconv = [float(ydeconv[i]) for i in range(len(ydeconv))]
846
847 print("")
848
849 return xGFFT, ydeconv, xRawFFT, yRawFFT, xRawFFT, yGFFT
850
851def deconvolute_deconvolve(xRaw, yRaw, xG, yG):
852 """
853 概要:
854 scipy.signal.deconvolve を用いて逆畳み込みを実行します。
855 詳細説明:
856 scipy.signal.deconvolve を直接使用して、観測データ yRaw からウィンドウ関数 yG を逆畳み込みします。
857 引数:
858 :param xRaw: 生データのx軸配列。
859 :type xRaw: list
860 :param yRaw: 生データのy軸配列。
861 :type yRaw: list
862 :param xG: ウィンドウ関数のx軸配列。
863 :type xG: list
864 :param yG: ウィンドウ関数のy軸配列。
865 :type yG: list
866 戻り値:
867 :returns: 逆畳み込み後のx軸配列とy軸配列のタプル。
868 :rtype: tuple of (list, numpy.ndarray)
869 """
870 k = sum(yG)
871
872 print("Deconvolution by scipy.signal.deconvove")
873 print("")
874 IDec, remainder = scipy.signal.deconvolve(yRaw, yG)
875 IDec *= k
876 ndata = len(xRaw)
877 nGhalf = int(len(xG) / 2)
878
879 return xRaw[nGhalf:ndata-nGhalf], IDec
880
881
882def make_edge_correction_weights(xRaw, whalf, nGmax, w_floor=1.0e-6):
883 """
884 概要:
885 Ridge回帰のような手法で使用されるエッジ補正重みを作成します。
886 詳細説明:
887 データ範囲の端で係数が不自然に大きくなるのを防ぐための重みリストを生成します。
888 重みは畳み込み関数の累積和に基づいて計算されます。
889 引数:
890 :param xRaw: 生データのx軸配列。
891 :type xRaw: numpy.ndarray
892 :param whalf: 半値半幅(HWHM)。
893 :type whalf: float
894 :param nGmax: 畳み込み関数の最大インデックス範囲。
895 :type nGmax: int
896 :param w_floor: 重みの最小値。これより小さくならないようにクリップされます。デフォルトは1.0e-6。
897 :type w_floor: float
898 戻り値:
899 :returns: エッジ補正重みのリスト。
900 :rtype: list
901 """
902 n = len(xRaw)
903 w = [0.0]
904 for i in range(1, n):
905 w.append(w[i - 1] + convolve_func(xRaw[i], 0.0, whalf))
906 wGmax = w[n - 1]
907 if abs(wGmax) < w_floor:
908 return [1.0] * n
909 return [max(1.0 - w[i] / wGmax, w_floor) for i in range(n)]
910
911
912def edge_weight_at(w, n, nGmax, j, w_floor=1.0e-6):
913 """
914 概要:
915 係数インデックス j に対する対称的なエッジ補正重みを返します。
916 詳細説明:
917 与えられた重みリスト w とインデックス j に基づいて、対称的なエッジ補正重みを計算します。
918 引数:
919 :param w: エッジ補正重み配列。
920 :type w: list
921 :param n: データ点の総数。
922 :type n: int
923 :param nGmax: 畳み込み関数の最大インデックス範囲。
924 :type nGmax: int
925 :param j: 係数のインデックス。
926 :type j: int
927 :param w_floor: 重みの最小値。これより小さくならないようにクリップされます。デフォルトは1.0e-6。
928 :type w_floor: float
929 戻り値:
930 :returns: 係数インデックス j におけるエッジ補正重み。
931 :rtype: float
932 """
933 if j <= nGmax:
934 wj = w[j]
935 elif n - 1 - j < nGmax:
936 wj = w[n - 1 - j]
937 else:
938 wj = w[nGmax - 1]
939 return max(wj, w_floor)
940
941
942def make_basis_matrix(xRaw, whalf, w=None, nGmax=None):
943 """
944 概要:
945 装置関数の稠密な基底行列を構築します。
946 詳細説明:
947 K[i, j] は係数 j を観測点 i にマッピングします。
948 エッジ重みが提供された場合、各基底列は同じ補正で使用されます。
949 引数:
950 :param xRaw: 生データのx軸配列。
951 :type xRaw: numpy.ndarray or list
952 :param whalf: 半値半幅(HWHM)。
953 :type whalf: float
954 :param w: エッジ補正重み配列。デフォルトはNone。
955 :type w: list or None
956 :param nGmax: 畳み込み関数の最大インデックス範囲。デフォルトはNone。
957 :type nGmax: int or None
958 戻り値:
959 :returns: 装置関数の基底行列。
960 :rtype: numpy.ndarray
961 """
962 x = np.asarray(xRaw, dtype=float)
963 n = len(x)
964 K = np.empty((n, n), dtype=float)
965 for i in range(n):
966 xi = x[i]
967 for j in range(n):
968 v = convolve_func(xi, x[j], whalf)
969 if w is not None and nGmax is not None:
970 v /= edge_weight_at(w, n, nGmax, j)
971 K[i, j] = v
972 return K
973
974
975def make_first_difference_matrix(n):
976 """
977 概要:
978 平滑化ペナルティのための1階差分行列 D を返します。
979 詳細説明:
980 D @ c は [c[1]-c[0], c[2]-c[1], ...] のようなベクトルを生成します。
981 このベクトルにペナルティを課すことで、係数の急激な振動を抑制します。
982 引数:
983 :param n: 係数の数。
984 :type n: int
985 戻り値:
986 :returns: 1階差分行列。
987 :rtype: numpy.ndarray
988 """
989 if n < 2:
990 return np.zeros((0, n), dtype=float)
991 D = np.zeros((n - 1, n), dtype=float)
992 for i in range(n - 1):
993 D[i, i] = -1.0
994 D[i, i + 1] = 1.0
995 return D
996
997
998def deconvolute_nnls(xRaw, yRaw, xG, yG):
999 """
1000 概要:
1001 非負最小二乗法(NNLS)を用いて逆畳み込みを実行します。
1002 詳細説明:
1003 観測データ yRaw と装置関数の基底 K を用いて、係数が非負であるという制約の下で
1004 最小二乗問題を解きます。任意でRidgeまたはSmoothな正則化を適用できます。
1005 引数:
1006 :param xRaw: 生データのx軸配列。
1007 :type xRaw: numpy.ndarray or list
1008 :param yRaw: 生データのy軸配列。
1009 :type yRaw: numpy.ndarray or list
1010 :param xG: ウィンドウ関数のx軸配列。
1011 :type xG: list
1012 :param yG: ウィンドウ関数のy軸配列。
1013 :type yG: list
1014 戻り値:
1015 :returns: 逆畳み込み後のx軸配列とy軸配列のタプル。
1016 :rtype: tuple of (numpy.ndarray, list)
1017 """
1018 global Wa, Grange, alpha, nnls_maxiter, nnls_penalty
1019
1020 dx = abs(xRaw[1] - xRaw[0])
1021
1022 print("Deconvolution by non-negative least squares (NNLS)")
1023 print(" Constraint: all Gaussian/Lorentzian basis coefficients >= 0")
1024 if alpha > 0.0:
1025 print(f" Regularization: {nnls_penalty} penalty with alpha={alpha:g}")
1026 else:
1027 print(" Regularization: none; pure NNLS")
1028 print("")
1029
1030 n = len(xRaw)
1031 nGmax = int(10.0 * Wa / dx + 1.0001)
1032 w = make_edge_correction_weights(xRaw, Wa, nGmax)
1033 K = make_basis_matrix(xRaw, Wa, w=w, nGmax=nGmax)
1034
1035 yvec = np.asarray(yRaw, dtype=float)
1036
1037 if alpha > 0.0:
1038 if nnls_penalty == 'smooth':
1039 R = make_first_difference_matrix(n)
1040 else:
1041 R = np.eye(n, dtype=float)
1042
1043 A = np.vstack([K, sqrt(alpha) * R])
1044 b = np.concatenate([yvec, np.zeros(R.shape[0], dtype=float)])
1045
1046 result = scipy.optimize.lsq_linear(
1047 A,
1048 b,
1049 bounds=(0.0, np.inf),
1050 max_iter=nnls_maxiter,
1051 )
1052 coeff = result.x
1053 rnorm = float(np.linalg.norm(K @ coeff - yvec))
1054 regnorm = float(np.linalg.norm(R @ coeff))
1055 print(f" bounded LS status = {result.status}: {result.message}")
1056 print(f" data residual norm = {rnorm:g}")
1057 print(f" penalty norm = {regnorm:g}")
1058 else:
1059 coeff, rnorm = scipy.optimize.nnls(K, yvec, maxiter=nnls_maxiter)
1060 print(f" NNLS residual norm = {rnorm:g}")
1061
1062 y = coeff / dx
1063
1064 n_active = int(np.count_nonzero(coeff > 0.0))
1065 print(f" active coefficients = {n_active} / {n}")
1066 print(f" coefficient range = {coeff.min():g} - {coeff.max():g}")
1067 print("")
1068
1069 return xRaw, y.tolist()
1070
1071def deconvolute_ridge(xRaw, yRaw, xG, yG):
1072 """
1073 概要:
1074 Ridge回帰を用いて逆畳み込みを実行します。
1075 詳細説明:
1076 観測データ yRaw からRidge回帰により逆畳み込み係数を推定します。
1077 エッジ補正重みと正則化パラメータ alpha を使用します。
1078 引数:
1079 :param xRaw: 生データのx軸配列。
1080 :type xRaw: list
1081 :param yRaw: 生データのy軸配列。
1082 :type yRaw: list
1083 :param xG: ウィンドウ関数のx軸配列。
1084 :type xG: list
1085 :param yG: ウィンドウ関数のy軸配列。
1086 :type yG: list
1087 戻り値:
1088 :returns: 畳み込み後のx軸配列とy軸配列のタプル。
1089 :rtype: tuple of (list, list)
1090 """
1091 global Wa, Grange
1092
1093 k = sum(yG)
1094 dx = abs(xRaw[1] - xRaw[0])
1095
1096 print("Deconvolution by Ridge regression")
1097 print("")
1098
1099 nGmax = int(10.0 * Wa / dx + 1.0001)
1100# print("nGmax=", nGmax)
1101
1102 w = [0.0]
1103 n = len(xRaw)
1104 for i in range(1, len(xRaw)):
1105 w.append(w[i-1] + convolve_func(xRaw[i], 0, Wa))
1106 wGmax = w[n-1]
1107 w_floor = 1.0e-6
1108 w = [max(1.0 - w[i] / wGmax, w_floor) for i in range(n)]
1109# w = [1.0 - w[i] / wGmax for i in range(n)]
1110
1111 y = Ridge(xRaw, yRaw, len(xRaw), lambda x1, x2: convolve_func(x1, x2, Wa), w, nGmax, alpha)
1112 y = [v / dx for v in y]
1113
1114 return xRaw, y
1115
1116def deconvolute_sp(xRaw, yRaw, xG, yG, nsmooth):
1117 """
1118 概要:
1119 Smoothing Penalty法を用いて逆畳み込みを実行します。
1120 詳細説明:
1121 観測データ yRaw からSmoothing Penalty回帰により逆畳み込み係数を推定します。
1122 エッジ補正重み、正則化パラメータ alpha、および平滑化幅 nsmooth を使用します。
1123 引数:
1124 :param xRaw: 生データのx軸配列。
1125 :type xRaw: list
1126 :param yRaw: 生データのy軸配列。
1127 :type yRaw: list
1128 :param xG: ウィンドウ関数のx軸配列。
1129 :type xG: list
1130 :param yG: ウィンドウ関数のy軸配列。
1131 :type yG: list
1132 :param nsmooth: 平滑化の幅。
1133 :type nsmooth: int
1134 戻り値:
1135 :returns: 逆畳み込み後のx軸配列とy軸配列のタプル。
1136 :rtype: tuple of (list, list)
1137 """
1138 global Wa, Grange
1139
1140 k = sum(yG)
1141 dx = abs(xRaw[1] - xRaw[0])
1142
1143 print("Deconvolution by smoothing penalty method")
1144 print("")
1145
1146 nGmax = int(10.0 * Wa / dx + 1.0001)
1147# print("nGmax=", nGmax)
1148
1149 w_floor = 1.0e-6
1150 w = [w_floor]
1151# w = [0.0]
1152 n = len(xRaw)
1153 for i in range(1, len(xRaw)):
1154 w.append(w[i-1] + convolve_func(xRaw[i], 0, Wa))
1155 wGmax = w[n-1]
1156 w = [max(1.0 - w[i] / wGmax, w_floor) for i in range(n)]
1157# w = [1.0 - w[i] / wGmax for i in range(n)]
1158
1159 y = Smoothing_Penalty_Regression(xRaw, yRaw, len(xRaw),
1160 lambda x1, x2: convolve_func(x1, x2, Wa), w, nGmax, alpha, nsmooth)
1161 y = [v / dx for v in y]
1162
1163 return xRaw, y
1164
1165#window = None
1166def _set_iteration_ylim(ax0, yRaw, y):
1167 """
1168 概要:
1169 反復プロットのy軸範囲を安定して設定します。
1170 詳細説明:
1171 生データ yRaw と更新されたデータ y に基づいて、Matplotlibの軸 ax0 のy軸範囲を
1172 動的に調整します。これにより、反復処理中のグラフ表示が安定します。
1173 引数:
1174 :param ax0: Matplotlibの軸オブジェクト。
1175 :type ax0: matplotlib.axes.Axes
1176 :param yRaw: 生データのy軸配列。
1177 :type yRaw: list or numpy.ndarray
1178 :param y: 更新されたデータ(反復結果)のy軸配列。
1179 :type y: list or numpy.ndarray
1180 """
1181 ymin = min([0.0, min(yRaw), min(y)])
1182 ymax = max([0.0, max(yRaw), max(y)])
1183 if ymax <= ymin:
1184 ymax = ymin + 1.0
1185 margin = 0.05 * (ymax - ymin)
1186 ax0.set_ylim([ymin - margin, ymax + margin])
1187
1188
1189def _init_iteration_plot(fig, ax0, xRaw, yRaw, y, xgmin, xgmax):
1190 """
1191 概要:
1192 反復更新グラフを一度だけ初期化します。
1193 詳細説明:
1194 反復処理の開始時にグラフをセットアップし、rawデータと初期の更新データをプロットします。
1195 Line2Dオブジェクトを再利用することで、反復ごとの描画コストを削減します。
1196 引数:
1197 :param fig: MatplotlibのFigureオブジェクト。
1198 :type fig: matplotlib.pyplot.Figure
1199 :param ax0: Matplotlibの軸オブジェクト。
1200 :type ax0: matplotlib.axes.Axes
1201 :param xRaw: 生データのx軸配列。
1202 :type xRaw: list or numpy.ndarray
1203 :param yRaw: 生データのy軸配列。
1204 :type yRaw: list or numpy.ndarray
1205 :param y: 現在の更新データ(反復結果)のy軸配列。
1206 :type y: list or numpy.ndarray
1207 :param xgmin: x軸の最小表示範囲。
1208 :type xgmin: float
1209 :param xgmax: x軸の最大表示範囲。
1210 :type xgmax: float
1211 戻り値:
1212 :returns: 更新されるプロットのLine2Dオブジェクト。
1213 :rtype: matplotlib.lines.Line2D
1214 """
1215 ax0.cla()
1216 ax0.plot(xRaw, yRaw, label = 'raw/initial')
1217 line_updated, = ax0.plot(xRaw, y, label = 'updated')
1218 ax0.set_xlim([xgmin, xgmax])
1219 _set_iteration_ylim(ax0, yRaw, y)
1220 ax0.legend()
1221
1222 # Layout adjustment is expensive; do it once before the loop.
1223 app.plotevent.layout()
1224 fig.canvas.draw_idle()
1225 fig.canvas.flush_events()
1226 plt.pause(max(sleeptime, 0.05))
1227 return line_updated
1228
1229
1230def _update_iteration_plot(fig, ax0, line_updated, yRaw, y):
1231 """
1232 概要:
1233 反復逆畳み込み中にyデータのみを更新します。
1234 詳細説明:
1235 既存のLine2Dオブジェクトのyデータを更新し、グラフのy軸範囲を調整して再描画します。
1236 これにより、高速な更新が可能になります。
1237 引数:
1238 :param fig: MatplotlibのFigureオブジェクト。
1239 :type fig: matplotlib.pyplot.Figure
1240 :param ax0: Matplotlibの軸オブジェクト。
1241 :type ax0: matplotlib.axes.Axes
1242 :param line_updated: 更新されるプロットのLine2Dオブジェクト。
1243 :type line_updated: matplotlib.lines.Line2D
1244 :param yRaw: 生データのy軸配列。y軸範囲調整のために使用されます。
1245 :type yRaw: list or numpy.ndarray
1246 :param y: 更新されたデータ(反復結果)のy軸配列。
1247 :type y: list or numpy.ndarray
1248 """
1249 line_updated.set_ydata(y)
1250 _set_iteration_ylim(ax0, yRaw, y)
1251 fig.canvas.draw_idle()
1252 fig.canvas.flush_events()
1253 plt.pause(max(sleeptime, 0.05))
1254
1255
1256def deconvolute_jacobi(xRaw, yRaw, xG, yG, fig, ax):
1257 """
1258 概要:
1259 Jacobi法を用いて逆畳み込みを実行します。
1260 詳細説明:
1261 反復計算により、畳み込み方程式をJacobi法で解き、データを逆畳み込みします。
1262 途中で平滑化や負値クリッピングを行い、グラフをリアルタイムで更新します。
1263 収束条件または最大反復回数に達するまで処理を続行します。
1264 引数:
1265 :param xRaw: 生データのx軸配列。
1266 :type xRaw: list
1267 :param yRaw: 生データのy軸配列。
1268 :type yRaw: list
1269 :param xG: ウィンドウ関数のx軸配列。
1270 :type xG: list
1271 :param yG: ウィンドウ関数のy軸配列。
1272 :type yG: list
1273 :param fig: MatplotlibのFigureオブジェクト。
1274 :type fig: matplotlib.pyplot.Figure
1275 :param ax: Matplotlibの軸オブジェクトのリスト。最初の要素が使用されます。
1276 :type ax: list of matplotlib.axes.Axes
1277 戻り値:
1278 :returns: 逆畳み込み後のx軸配列とy軸配列のタプル。
1279 :rtype: tuple of (list, list)
1280 """
1281 global Wa, Grange
1282# global window
1283
1284 k = sum(yG)
1285
1286 print("Deconvolution by Jacobi method")
1287 print("")
1288
1289 xstep = xRaw[1] - xRaw[0]
1290
1291 xgmin = min(xRaw)
1292 xgmax = max(xRaw)
1293
1294 n = len(xRaw)
1295 Sg = np.zeros(n)
1296 for i in range(n):
1297 for j in range(n):
1298 Sg[i] += Hij(xstep, Wa, Grange, i, j)
1299 print("Filter area w.r.t. i: Sg=", Sg[int(n/2)])
1300
1301 ymax = max([abs(yRaw[i]) for i in range(n)])
1302 y = yRaw.copy()
1303 yPrev = yRaw.copy()
1304
1305 line_updated = _init_iteration_plot(fig, ax[0], xRaw, yRaw, y, xgmin, xgmax)
1306
1307 for it in range(nmaxiter):
1308 Hx = np.zeros(n)
1309 print("iter=", it)
1310
1311 for i in range(n):
1312 for j in range(n):
1313 Hx[i] += Hij(xstep, Wa, Grange, i, j) * yPrev[j]
1314 h = Hij(xstep, Wa, Grange, i, i)
1315 y[i] = yPrev[i] + (yRaw[i] - Hx[i] / Sg[i]) / h * dump
1316
1317# y = SmoothingBySimpleAverage(y, nsmooth)
1318 y = SmoothingByPolynomialFit(y, nsmooth)
1319 if zero_correction:
1320 for i in range(n):
1321 if y[i] < 0.0:
1322 y[i] = 0.0
1323
1324# w = plt.get_current_fig_manager().window
1325# if w != window:
1326 if app.plotevent.stop_flag:
1327 print("Terminated by user")
1328 break
1329
1330 _update_iteration_plot(fig, ax[0], line_updated, yRaw, y)
1331
1332 max_err = max([abs(y[i] - yPrev[i]) for i in range(n)])
1333 rel_err = max_err / ymax
1334 print(" max error: ", max_err, " relative error: ", rel_err, " eps=", eps)
1335 if max_err / ymax < eps:
1336 print("Converged at max_err={} ({} relative) < {}".format(max_err, rel_err, eps))
1337 break
1338
1339 yPrev = y.copy()
1340 else:
1341 print("Not converged")
1342
1343 return xRaw, y
1344
1345
1346def deconvolute_gauss_seidel(xRaw, yRaw, xG, yG, fig, ax):
1347 """
1348 概要:
1349 Gauss-Seidel法を用いて逆畳み込みを実行します。
1350 詳細説明:
1351 反復計算により、畳み込み方程式をGauss-Seidel法で解き、データを逆畳み込みします。
1352 途中で平滑化や負値クリッピングを行い、グラフをリアルタイムで更新します。
1353 収束条件または最大反復回数に達するまで処理を続行します。
1354 引数:
1355 :param xRaw: 生データのx軸配列。
1356 :type xRaw: list
1357 :param yRaw: 生データのy軸配列。
1358 :type yRaw: list
1359 :param xG: ウィンドウ関数のx軸配列。
1360 :type xG: list
1361 :param yG: ウィンドウ関数のy軸配列。
1362 :type yG: list
1363 :param fig: MatplotlibのFigureオブジェクト。
1364 :type fig: matplotlib.pyplot.Figure
1365 :param ax: Matplotlibの軸オブジェクトのリスト。最初の要素が使用されます。
1366 :type ax: list of matplotlib.axes.Axes
1367 戻り値:
1368 :returns: 逆畳み込み後のx軸配列とy軸配列のタプル。
1369 :rtype: tuple of (list, list)
1370 """
1371 global Wa, Grange
1372# global window
1373
1374 k = sum(yG)
1375
1376 print("Deconvolution by Gauss-Seidel method")
1377 print("")
1378
1379 xstep = xRaw[1] - xRaw[0]
1380
1381 xgmin = min(xRaw)
1382 xgmax = max(xRaw)
1383
1384 n = len(xRaw)
1385 Sg = np.zeros(n)
1386 for i in range(n):
1387 for j in range(n):
1388 Sg[i] += Hij(xstep, Wa, Grange, i, j)
1389 print("Filter area w.r.t. i: Sg=", Sg[int(n/2)])
1390
1391 ymax = max([abs(yRaw[i]) for i in range(n)])
1392 y = yRaw.copy()
1393 yPrev = yRaw.copy()
1394
1395 line_updated = _init_iteration_plot(fig, ax[0], xRaw, yRaw, y, xgmin, xgmax)
1396
1397 for it in range(nmaxiter):
1398 Hx = np.zeros(n)
1399 print(f"iter={it:4} ", end = '')
1400
1401 for i in range(n):
1402 for j in range(i):
1403 Hx[i] += Hij(xstep, Wa, Grange, i, j) * y[j]
1404 for j in range(i, n):
1405 Hx[i] += Hij(xstep, Wa, Grange, i, j) * yPrev[j]
1406 h = Hij(xstep, Wa, Grange, i, i)
1407 y[i] = yPrev[i] + (yRaw[i] - Hx[i] / Sg[i]) / h * dump
1408
1409# y = SmoothingBySimpleAverage(y, nsmooth)
1410 y = SmoothingByPolynomialFit(y, nsmooth)
1411 if zero_correction:
1412 for i in range(n):
1413 if y[i] < 0.0:
1414 y[i] = 0.0
1415
1416# w = plt.get_current_fig_manager().window
1417# if w != window:
1418 if app.plotevent.stop_flag:
1419 print("Terminated by user")
1420 break
1421
1422 _update_iteration_plot(fig, ax[0], line_updated, yRaw, y)
1423
1424 max_err = max([abs(y[i] - yPrev[i]) for i in range(n)])
1425 rel_err = max_err / ymax
1426 print(f" max error: {max_err:10.4g} relative error: {rel_err:10.4g} eps={eps:10.4g}")
1427 if max_err / ymax < eps:
1428 print("Converged at max_err={} ({} relative) < {}".format(max_err, rel_err, eps))
1429 break
1430
1431 yPrev = y.copy()
1432 else:
1433 print("Not converged")
1434
1435 return xRaw, y
1436
1437
1438
1439
1440def plot_input_data(xRaw, yRaw, label_x='', label_y=''):
1441 """
1442 概要:
1443 選択された入力データのみをプロットします。
1444 詳細説明:
1445 逆畳み込みを実行せず、指定された入力データをグラフに表示するだけのモードです。
1446 逆畳み込みの結果はExcelファイルに出力されません。
1447 引数:
1448 :param xRaw: 入力データのx軸配列。
1449 :type xRaw: list
1450 :param yRaw: 入力データのy軸配列。
1451 :type yRaw: list
1452 :param label_x: x軸のラベル文字列。
1453 :type label_x: str
1454 :param label_y: y軸のラベル文字列。
1455 :type label_y: str
1456 """
1457 print("Plot input data only")
1458 print(" No deconvolution is performed.")
1459 print(" No deconvoluted Excel output is written.")
1460 print("")
1461
1462 fig = plt.figure(figsize=figsize)
1463 ax0 = fig.add_subplot(1, 1, 1)
1464
1465 app.plotevent = tkPlotEvent(plt)
1466 app.plotevent.add_button()
1467
1468 ax0.plot(xRaw, yRaw, label='input')
1469 ax0.set_xlabel(str(label_x))
1470 ax0.set_ylabel(str(label_y))
1471 ax0.set_xlim([min(xRaw), max(xRaw)])
1472
1473 ymin = min(yRaw)
1474 ymax = max(yRaw)
1475 if ymax <= ymin:
1476 ymax = ymin + 1.0
1477 margin = 0.05 * (ymax - ymin)
1478 ax0.set_ylim([ymin - margin, ymax + margin])
1479 ax0.legend()
1480
1481 app.plotevent.layout()
1482 fig.canvas.draw_idle()
1483 fig.canvas.flush_events()
1484 plt.pause(0.1)
1485
1486 app.plotevent.finalize_button()
1487 app.terminate("Press ENTER to exit>>", usage=lambda app: usage(app), pause=True)
1488
1489
1490#======================
1491# main
1492#======================
1493def main():
1494 """
1495 概要:
1496 メイン処理を実行します。
1497 詳細説明:
1498 コマンドライン引数を解析し、入力データを読み込み、
1499 選択されたモードに基づいて逆畳み込み処理を実行します。
1500 結果はプロットされ、Excelファイルに保存されます。
1501 """
1502 global xmin, xmax
1503
1504 args = build_arg_parser().parse_args()
1505 apply_args_to_globals(args)
1506
1507 logfile = app.replace_path(infile)
1508 outfile = app.replace_path(infile, template = ["{dirname}", "{filebody}-deconvoluted.xlsx"])
1509 print(f"Open logfile [{logfile}]")
1510 app.redirect(targets = ["stdout", logfile], mode = 'w')
1511
1512 print("")
1513 print(f"Process data in [{infile}] with {func_type} function of width={Wa}")
1514 print("infile : ", infile)
1515 print("xlabel : ", xlabel)
1516 print("ylabel : ", ylabel)
1517 print("outfile : ", outfile)
1518 print("mode : ", mode)
1519 print("flip_x : ", flip_x)
1520 if mode == 'convolve' or mode == 'fft':
1521 print("For mode = 'convolve' or 'fft':")
1522 print(" convmode: ", convmode)
1523 print(" x range : ", xmin, xmax)
1524 if mode == 'gs' or mode == 'jacobi':
1525 print(" x range : ", xmin, xmax)
1526 print("For mode = 'gs' or 'jacobi':")
1527 print(" dump=", dump)
1528 print(" nmaxiter=", nmaxiter)
1529 print(" eps=", eps)
1530 print(" nsmooth=", nsmooth)
1531 print(" zero correction=", zero_correction)
1532 if mode in ('ridge', 'sp'):
1533 print(" alpha=", alpha)
1534 if mode == 'nnls':
1535 print("For mode = 'nnls':")
1536 print(" non-negative coefficients: enabled")
1537 print(" alpha=", alpha)
1538 if alpha > 0.0:
1539 print(" regularization penalty=", nnls_penalty)
1540 else:
1541 print(" regularization penalty= none (pure NNLS)")
1542 print(" nnls_maxiter=", nnls_maxiter)
1543 if mode == 'plot':
1544 print("For mode = 'plot':")
1545 print(" plot selected input data only")
1546 print("")
1547
1548 if isinstance(xlabel, str) and '*** choose ***' in xlabel:
1549 app.terminate(f"\nError: Choose x label\nTerminated.\n", usage = lambda app: usage(app), pause = True)
1550 if isinstance(ylabel, str) and '*** choose ***' in ylabel:
1551 app.terminate(f"\nError: Choose y label\nTerminated.\n", usage = lambda app: usage(app), pause = True)
1552
1553 print("Input data")
1554 print("X range to be calculated:", xmin, "-", xmax)
1555 datafile = tkVariousData(infile)
1556 header, datalist = datafile.Read_minimum_matrix(close_fp = True, usage = lambda app: usage(app))
1557 label_x, _xin = datafile.FindDataArray(xlabel, flag = 'i')
1558 label_y, _yin = datafile.FindDataArray(ylabel, flag = 'i')
1559 if _xin is None:
1560 app.terminate(f"Error: Can not find the data with [{xlabel}]", usage = lambda app: usage(app), pause = True)
1561 if _yin is None:
1562 app.terminate(f"Error: Can not find the data with [{xlabel}]", usage = lambda app: usage(app), pause = True)
1563
1564 xin_all = []
1565 yin_all = []
1566 for i in range(len(_xin)):
1567 xval = -_xin[i] if flip_x else _xin[i]
1568 xin_all.append(xval)
1569 yin_all.append(_yin[i])
1570
1571 if flip_x:
1572 print(" flip_x=1: multiply input x values by -1 before filtering/calculation.")
1573
1574 xin = []
1575 yin = []
1576 for xval, yval in zip(xin_all, yin_all):
1577 if not (xmin is None or xmin == '' or xmin == '*') and xval < xmin:
1578 continue
1579 if not (xmax is None or xmax == '' or xmax == '*') and xmax < xval:
1580 continue
1581 xin.append(xval)
1582 yin.append(yval)
1583# print("x=", xin)
1584# print("y=", yin)
1585
1586 if len(xin) < 2:
1587 app.terminate(
1588 f"\nError in main(): Need at least two data points after x-range filtering.\n",
1589 usage = lambda app: usage(app), pause = True,
1590 )
1591
1592 # Internally use ascending x order. This avoids negative xstep problems in
1593 # sampled filters and keeps coefficient-to-spectrum scaling positive.
1594 # Descending binding-energy axes are common in photoemission data.
1595 if xin[1] < xin[0]:
1596 print(" Input x axis is descending; reverse data internally for calculation.")
1597 xin = list(reversed(xin))
1598 yin = list(reversed(yin))
1599 elif any(xin[i + 1] < xin[i] for i in range(len(xin) - 1)):
1600 print(" Input x axis is not monotonic; sort data by x for calculation.")
1601 xy = sorted(zip(xin, yin), key=lambda t: t[0])
1602 xin = [v[0] for v in xy]
1603 yin = [v[1] for v in xy]
1604
1605 nindata = len(xin)
1606 print(" ndata = ", nindata)
1607 if xmin is None or xmin == '' or xmin == '*':
1608 xmin = min(xin)
1609 if xmax is None or xmax == '' or xmax == '*':
1610 xmax = max(xin)
1611 print("x range=", xmin, xmax)
1612
1613 xinmin = xin[0]
1614 xinstep = xin[1] - xin[0]
1615 if xinstep == 0.0:
1616 app.terminate(f"\nError in main(): xin[0] and xin[1] are the same. Check input file [{infile}].\n",
1617 usage = lambda app: usage(app), pause = True)
1618
1619 xinmax = xinmin + xinstep * (nindata - 1)
1620 print(" x range: {} - {} at {} step".format(xinmin, xinmax, xinstep))
1621 print("Smooth mode: ", smoothmode)
1622 print("")
1623
1624 xRaw = xin
1625 yRaw = yin
1626 xstep = xinstep
1627
1628 if mode == 'plot':
1629 plot_input_data(xRaw, yRaw, label_x=label_x, label_y=label_y)
1630 return
1631
1632 print("Filter: Wa = {} Grange = {}".format(Wa, Grange))
1633 xG, yG = make_wf(Wa, Grange, xstep)
1634 print("")
1635
1636 fig = plt.figure(figsize = figsize)
1637 ax = []
1638 ax.append(fig.add_subplot(3, 1, 1))
1639 ax.append(fig.add_subplot(3, 1, 2))
1640 ax.append(fig.add_subplot(3, 1, 3))
1641# ax.append(fig.add_subplot(4, 1, 4))
1642
1643 app.plotevent = tkPlotEvent(plt)
1644 app.plotevent.add_button()
1645 app.plotevent.layout()
1646
1647 fig.canvas.draw_idle()
1648 fig.canvas.flush_events()
1649 plt.pause(0.1)
1650# window = plt.get_current_fig_manager().window
1651
1652# make data to be deconvolved
1653 nw = int(len(xG) / 2)
1654 if 'average' in smoothmode:
1655 yRaw = SmoothingBySimpleAverage(yRaw, 31)
1656 if 'extend' in smoothmode:
1657 xRaw, yRaw = extend_smooth(xRaw, yRaw, nw*kzero, nw*klin, xstep)
1658 if 'convolve' in smoothmode:
1659 xconv, yconv = convolve(xRaw, yRaw, yG, mode = convmode)
1660 else:
1661 xconv, yconv = xRaw, yRaw
1662
1663# deconvolution
1664 if mode == 'fft':
1665 xDec, yDec, xRawFFT, yRawFFT, xGFFT, yGFFT = deconvolute_fft(xconv, yconv, xG, yG)
1666 xconv = xRawFFT
1667 yconv = yRawFFT
1668 datafft = ax[2].plot(xGFFT, yGFFT, label = 'WF for FFT')
1669 elif mode == 'jacobi':
1670 xDec, yDec = deconvolute_jacobi(xconv, yconv, xG, yG, fig, ax)
1671 yG = [convolve_func(xRaw[i], 0.0, Wa) for i in range(len(xRaw))]
1672 datafft = ax[2].plot(xRaw, yG, label = 'WF for Jacobi method')
1673 elif mode == 'gs':
1674 xDec, yDec = deconvolute_gauss_seidel(xconv, yconv, xG, yG, fig, ax)
1675 yG = [convolve_func(xRaw[i], 0.0, Wa) for i in range(len(xRaw))]
1676 datafft = ax[2].plot(xRaw, yG, label = 'WF for Gauss-Seidel method')
1677 elif mode == 'sp':
1678 yconv = SmoothingByPolynomialFit(yconv, nsmooth)
1679 xDec, yDec = deconvolute_sp(xconv, yconv, xG, yG, nsmooth)
1680# yconv = SmoothingByPolynomialFit(yconv, nsmooth)
1681 datafft = ax[2].plot(xG, yG, label = 'WF for Gauss kernel regression')
1682 elif mode == 'ridge':
1683 yconv = SmoothingByPolynomialFit(yconv, nsmooth)
1684 xDec, yDec = deconvolute_ridge(xconv, yconv, xG, yG)
1685 datafft = ax[2].plot(xG, yG, label = 'WF for Ridge regression')
1686 elif mode == 'nnls':
1687 yconv = SmoothingByPolynomialFit(yconv, nsmooth)
1688 xDec, yDec = deconvolute_nnls(xconv, yconv, xG, yG)
1689 datafft = ax[2].plot(xG, yG, label = 'WF for NNLS')
1690 elif mode == 'convolve' or mode == 'deconvolve':
1691 xDec, yDec = deconvolute_deconvolve(xconv, yconv, xG, yG)
1692 datafft = ax[2].plot(xG, yG, label = 'WF for scipy.signal.deconvolve')
1693 else:
1694 raise ValueError(f"Unsupported mode: {mode}")
1695
1696 app.plotevent.finalize_button()
1697 if app.plotevent.stop_flag:
1698 print("")
1699 print("Iteration terminated by user")
1700 else:
1701 xgmin = min([min(xconv), min(xDec)])
1702 xgmax = max([max(xconv), max(xDec)])
1703
1704 ax[0].cla()
1705 data1 = ax[0].plot(xconv, yconv, label = 'input(convoluted)')
1706 data3 = ax[0].plot(xDec, yDec, label = 'deconvoluted')
1707 data1 = ax[1].plot(xRaw, yRaw, label = 'input(raw)')
1708 data2 = ax[1].plot(xconv, yconv, label = 'input(convoluted)')
1709# data5 = ax[2].plot(xDec, yDec, label = 'deconvoluted')
1710# data6 = ax[2].plot([xgmin, xgmax], [0.0, 0.0], color = 'red', linestyle = 'dashed')
1711 ax[0].set_xlim([xgmin, xgmax])
1712 ax[0].set_ylim([0.0, max([max(yRaw), max(yDec)])])
1713 ax[1].set_xlim([xgmin, xgmax])
1714 ax[1].set_ylim([0.0, max(yRaw)])
1715# ax[2].set_xlim([xgmin, xgmax])
1716# ax[2].set_ylim([-100, 10000])
1717 ax[2].set_xlim([xgmin, xgmax])
1718 ax[2].set_ylim([0.0, max(yG) if len(yG) > 0 else 1.0])
1719# ax[1].set_ylim([0.0, max(yRaw)])
1720
1721 ax[0].legend()
1722 ax[1].legend()
1723 ax[2].legend()
1724# ax[3].legend()
1725
1726# plt.tight_layout()
1727 app.plotevent.layout()
1728
1729 fig.canvas.draw_idle()
1730 fig.canvas.flush_events()
1731 plt.pause(0.1)
1732
1733 print("")
1734 print("Save deconvoluted data to [{}]".format(outfile))
1735 df = pd.DataFrame(np.array([xconv, yconv, yDec]).T, columns = ['x', 'y(input)', 'y(deconvoluted)'])
1736# df.to_excel(outfile, index = False)
1737 data_list = df.values.tolist()
1738 tkVariousData().to_excel(outfile, list(df.columns), list(map(list, zip(*data_list))), template = template)
1739
1740 app.terminate("Press ENTER to exit>>", usage = lambda app: usage(app), pause = True)
1741
1742
1743if __name__ == '__main__':
1744 main()