petal-qc 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of petal-qc might be problematic. Click here for more details.
- petal_qc/BTreport/CheckBTtests.py +321 -0
- petal_qc/BTreport/__init__.py +0 -0
- petal_qc/BTreport/bustapeReport.py +144 -0
- petal_qc/__init__.py +14 -0
- petal_qc/metrology/Cluster.py +90 -0
- petal_qc/metrology/DataFile.py +47 -0
- petal_qc/metrology/PetalMetrology.py +327 -0
- petal_qc/metrology/__init__.py +0 -0
- petal_qc/metrology/all2csv.py +57 -0
- petal_qc/metrology/analyze_locking_points.py +597 -0
- petal_qc/metrology/cold_noise.py +106 -0
- petal_qc/metrology/comparisonTable.py +59 -0
- petal_qc/metrology/convert_mitutoyo.py +175 -0
- petal_qc/metrology/convert_smartscope.py +145 -0
- petal_qc/metrology/coreMetrology.py +402 -0
- petal_qc/metrology/data2csv.py +63 -0
- petal_qc/metrology/do_Metrology.py +117 -0
- petal_qc/metrology/flatness4nigel.py +157 -0
- petal_qc/metrology/gtkutils.py +120 -0
- petal_qc/metrology/petal_flatness.py +353 -0
- petal_qc/metrology/show_data_file.py +118 -0
- petal_qc/metrology/testSummary.py +37 -0
- petal_qc/metrology/test_paralelism.py +71 -0
- petal_qc/thermal/CSVImage.py +69 -0
- petal_qc/thermal/DebugPlot.py +76 -0
- petal_qc/thermal/IRBFile.py +768 -0
- petal_qc/thermal/IRCore.py +110 -0
- petal_qc/thermal/IRDataGetter.py +359 -0
- petal_qc/thermal/IRPetal.py +1338 -0
- petal_qc/thermal/IRPetalParam.py +71 -0
- petal_qc/thermal/PetalColorMaps.py +62 -0
- petal_qc/thermal/Petal_IR_Analysis.py +142 -0
- petal_qc/thermal/PipeFit.py +598 -0
- petal_qc/thermal/__init__.py +0 -0
- petal_qc/thermal/analyze_IRCore.py +417 -0
- petal_qc/thermal/contours.py +378 -0
- petal_qc/thermal/create_IRCore.py +185 -0
- petal_qc/thermal/pipe_read.py +182 -0
- petal_qc/thermal/show_IR_petal.py +420 -0
- petal_qc/utils/Geometry.py +756 -0
- petal_qc/utils/Progress.py +182 -0
- petal_qc/utils/__init__.py +0 -0
- petal_qc/utils/all_files.py +35 -0
- petal_qc/utils/docx_utils.py +186 -0
- petal_qc/utils/fit_utils.py +188 -0
- petal_qc/utils/utils.py +180 -0
- petal_qc-0.0.0.dist-info/METADATA +29 -0
- petal_qc-0.0.0.dist-info/RECORD +51 -0
- petal_qc-0.0.0.dist-info/WHEEL +5 -0
- petal_qc-0.0.0.dist-info/entry_points.txt +3 -0
- petal_qc-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""An object usefull to show the progress of a process."""
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GTimer(object):
|
|
8
|
+
"""A timer."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, do_start=False):
|
|
11
|
+
"""Initialization.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
----
|
|
15
|
+
do_start: If True, start the timer. Defaults to False.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
self._start = time.time()
|
|
19
|
+
self._running = True
|
|
20
|
+
self._end = self._start
|
|
21
|
+
self._mark = self._start
|
|
22
|
+
if do_start:
|
|
23
|
+
self.start()
|
|
24
|
+
|
|
25
|
+
def mark(self):
|
|
26
|
+
"""Time since last mark."""
|
|
27
|
+
return time.time() - self._mark
|
|
28
|
+
|
|
29
|
+
def set_mark(self):
|
|
30
|
+
"""Sets a time mark."""
|
|
31
|
+
self._mark = time.time()
|
|
32
|
+
|
|
33
|
+
def start(self):
|
|
34
|
+
"""Start the timer."""
|
|
35
|
+
self._running = True
|
|
36
|
+
self._start = time.time()
|
|
37
|
+
|
|
38
|
+
def stop(self):
|
|
39
|
+
"""Stop the timer."""
|
|
40
|
+
self._end = time.time()
|
|
41
|
+
self._running = False
|
|
42
|
+
return self._end - self._start
|
|
43
|
+
|
|
44
|
+
def reset(self):
|
|
45
|
+
"""Reset the timer."""
|
|
46
|
+
self._start = time.time()
|
|
47
|
+
|
|
48
|
+
def __call__(self):
|
|
49
|
+
"""Get elapsed time since start."""
|
|
50
|
+
if self._running:
|
|
51
|
+
return time.time() - self._start
|
|
52
|
+
else:
|
|
53
|
+
return self._end - self._start
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def rate_units(r):
|
|
57
|
+
"""Return the rate as a string with proper unitns."""
|
|
58
|
+
units = "Hz"
|
|
59
|
+
if r > 1.0e6:
|
|
60
|
+
r /= 1.0e6
|
|
61
|
+
units = "MHz"
|
|
62
|
+
|
|
63
|
+
elif r > 1.0e3:
|
|
64
|
+
r /= 1.0e3
|
|
65
|
+
units = "kHz"
|
|
66
|
+
|
|
67
|
+
elif r > 1.0:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
elif r > 1e-3:
|
|
71
|
+
r /= 1.0e-3
|
|
72
|
+
units = "mHz"
|
|
73
|
+
|
|
74
|
+
else:
|
|
75
|
+
r /= 1.0e-6
|
|
76
|
+
units = "uHz"
|
|
77
|
+
|
|
78
|
+
return r, units
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def time_units(t):
|
|
82
|
+
"""Return time as string with proper units."""
|
|
83
|
+
units = "s"
|
|
84
|
+
if t > 86400.0:
|
|
85
|
+
t /= 86400.0
|
|
86
|
+
units = "d "
|
|
87
|
+
|
|
88
|
+
elif t > 3600.0:
|
|
89
|
+
t /= 3600.0
|
|
90
|
+
units = "h "
|
|
91
|
+
|
|
92
|
+
elif t > 60.0:
|
|
93
|
+
t /= 60.0
|
|
94
|
+
units = "m "
|
|
95
|
+
|
|
96
|
+
elif t > 1.0:
|
|
97
|
+
units = "s "
|
|
98
|
+
|
|
99
|
+
elif t > 1.0e-3:
|
|
100
|
+
t /= 1.0e-3
|
|
101
|
+
units = "ms"
|
|
102
|
+
|
|
103
|
+
elif t > 1.0e-6:
|
|
104
|
+
t /= 1.0e-6
|
|
105
|
+
units = "us"
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
t /= 1.0e-9
|
|
109
|
+
units = "ns"
|
|
110
|
+
|
|
111
|
+
return t, units
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ShowProgress(object):
|
|
115
|
+
"""Shows the program status based on a counter."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, max_val, width=40):
|
|
118
|
+
"""Initialization.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
----
|
|
122
|
+
max_val: Max value
|
|
123
|
+
width (optional): The width of the message string.
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
self.width = width-2
|
|
127
|
+
self.timer = GTimer()
|
|
128
|
+
self.counter = 0.0
|
|
129
|
+
self.max_val = float(max_val)
|
|
130
|
+
self.prg = 0.0
|
|
131
|
+
|
|
132
|
+
def start(self):
|
|
133
|
+
"""Start the process monitor."""
|
|
134
|
+
self.counter = 0
|
|
135
|
+
self.timer.start()
|
|
136
|
+
|
|
137
|
+
def stop(self):
|
|
138
|
+
"""Stop th eprocess monitor."""
|
|
139
|
+
self.timer.stop()
|
|
140
|
+
|
|
141
|
+
def increase(self, val=1.0, show=False, interval=0.1):
|
|
142
|
+
"""Increase the counter and show message if requested.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
----
|
|
146
|
+
val: Value of increment.
|
|
147
|
+
show: True to show the message. Defaults to False.
|
|
148
|
+
interval: Inerval to update message.
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
self.counter += val
|
|
152
|
+
self.prg = self.counter/self.max_val
|
|
153
|
+
if show:
|
|
154
|
+
if self.timer.mark() > interval:
|
|
155
|
+
self.timer.set_mark()
|
|
156
|
+
self.show()
|
|
157
|
+
|
|
158
|
+
def show(self):
|
|
159
|
+
"""Shows message."""
|
|
160
|
+
self.show_stat(self.prg)
|
|
161
|
+
|
|
162
|
+
def show_stat(self, x):
|
|
163
|
+
"""Show status of input value."""
|
|
164
|
+
n21 = int(x*self.width)
|
|
165
|
+
n22 = int(self.width-n21-1)
|
|
166
|
+
|
|
167
|
+
c21 = n21*'='
|
|
168
|
+
c22 = n22*' '
|
|
169
|
+
if self.prg > 0.0:
|
|
170
|
+
tt = self.timer()*(1.0/self.prg-1.0)
|
|
171
|
+
else:
|
|
172
|
+
tt = 0.0
|
|
173
|
+
|
|
174
|
+
rate = self.counter/self.timer()
|
|
175
|
+
rv, ru = rate_units(rate)
|
|
176
|
+
te, teu = time_units(self.timer())
|
|
177
|
+
tr, tru = time_units(tt)
|
|
178
|
+
|
|
179
|
+
# ss = '\r[%s>%s] %5.1f%% %8d' % (c21 , c22, 100.*x, self.counter)
|
|
180
|
+
ss = '\rElapsed %4.1f %s %5.1f %s [%s>%s] %5.1f%% ERT %5.1f %s' % (te, teu, rv, ru, c21, c22, 100.*x, tr, tru)
|
|
181
|
+
print(ss, end='')
|
|
182
|
+
sys.stdout.flush()
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""method to get all files in a folder matching the given name."""
|
|
2
|
+
import os
|
|
3
|
+
import fnmatch
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def all_files(root, patterns='*', single_level=False, yield_folders=False):
|
|
8
|
+
"""A generator that reruns all files in the given folder.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
----
|
|
12
|
+
root (file path): The folder
|
|
13
|
+
patterns (str, optional): The pattern of the files. Defaults to '*'.
|
|
14
|
+
single_level (bool, optional): If true, do not go into sub folders. Defaults to False.
|
|
15
|
+
yield_folders (bool, optional): If True, return folders as well. Defaults to False.
|
|
16
|
+
|
|
17
|
+
Yields
|
|
18
|
+
------
|
|
19
|
+
str: file path name
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
patterns = patterns.split(';')
|
|
23
|
+
for path, subdirs, files in os.walk(root):
|
|
24
|
+
if yield_folders:
|
|
25
|
+
files.extend(subdirs)
|
|
26
|
+
|
|
27
|
+
files.sort()
|
|
28
|
+
for name in files:
|
|
29
|
+
for pattern in patterns:
|
|
30
|
+
if fnmatch.fnmatch(name, pattern):
|
|
31
|
+
yield Path(os.path.join(path, name))
|
|
32
|
+
break
|
|
33
|
+
|
|
34
|
+
if single_level:
|
|
35
|
+
break
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Set of utilities to use wit hpython-docx."""
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
|
|
6
|
+
import docx
|
|
7
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
8
|
+
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
|
9
|
+
from docx.oxml import ns
|
|
10
|
+
from docx.oxml import OxmlElement
|
|
11
|
+
from docx.oxml import parse_xml
|
|
12
|
+
from docx.shared import Cm
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_element(name):
|
|
16
|
+
"""Create a new XML element."""
|
|
17
|
+
return OxmlElement(name)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_attribute(element, name, value):
|
|
21
|
+
"""Create an attribute."""
|
|
22
|
+
element.set(ns.qn(name), value)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def add_page_number(paragraph):
|
|
26
|
+
"""Add page number."""
|
|
27
|
+
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
|
28
|
+
|
|
29
|
+
page_run = paragraph.add_run()
|
|
30
|
+
t1 = create_element('w:t')
|
|
31
|
+
create_attribute(t1, 'xml:space', 'preserve')
|
|
32
|
+
t1.text = 'Page '
|
|
33
|
+
page_run._r.append(t1)
|
|
34
|
+
|
|
35
|
+
page_num_run = paragraph.add_run()
|
|
36
|
+
|
|
37
|
+
fldChar1 = create_element('w:fldChar')
|
|
38
|
+
create_attribute(fldChar1, 'w:fldCharType', 'begin')
|
|
39
|
+
|
|
40
|
+
instrText = create_element('w:instrText')
|
|
41
|
+
create_attribute(instrText, 'xml:space', 'preserve')
|
|
42
|
+
instrText.text = "PAGE"
|
|
43
|
+
|
|
44
|
+
fldChar2 = create_element('w:fldChar')
|
|
45
|
+
create_attribute(fldChar2, 'w:fldCharType', 'end')
|
|
46
|
+
|
|
47
|
+
page_num_run._r.append(fldChar1)
|
|
48
|
+
page_num_run._r.append(instrText)
|
|
49
|
+
page_num_run._r.append(fldChar2)
|
|
50
|
+
|
|
51
|
+
of_run = paragraph.add_run()
|
|
52
|
+
t2 = create_element('w:t')
|
|
53
|
+
create_attribute(t2, 'xml:space', 'preserve')
|
|
54
|
+
t2.text = ' of '
|
|
55
|
+
of_run._r.append(t2)
|
|
56
|
+
|
|
57
|
+
fldChar3 = create_element('w:fldChar')
|
|
58
|
+
create_attribute(fldChar3, 'w:fldCharType', 'begin')
|
|
59
|
+
|
|
60
|
+
instrText2 = create_element('w:instrText')
|
|
61
|
+
create_attribute(instrText2, 'xml:space', 'preserve')
|
|
62
|
+
instrText2.text = "NUMPAGES"
|
|
63
|
+
|
|
64
|
+
fldChar4 = create_element('w:fldChar')
|
|
65
|
+
create_attribute(fldChar4, 'w:fldCharType', 'end')
|
|
66
|
+
|
|
67
|
+
num_pages_run = paragraph.add_run()
|
|
68
|
+
num_pages_run._r.append(fldChar3)
|
|
69
|
+
num_pages_run._r.append(instrText2)
|
|
70
|
+
num_pages_run._r.append(fldChar4)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def paragraph_align_center():
|
|
74
|
+
"""Align center."""
|
|
75
|
+
return WD_ALIGN_PARAGRAPH.CENTER
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Document(object):
|
|
79
|
+
"""Create a document."""
|
|
80
|
+
|
|
81
|
+
def __init__(self):
|
|
82
|
+
"""Initialize."""
|
|
83
|
+
self.doc = docx.Document()
|
|
84
|
+
self.doc_fign = 1
|
|
85
|
+
self.doc_tbln = 1
|
|
86
|
+
|
|
87
|
+
def __getattr__(self, __name):
|
|
88
|
+
"""Call docx.document.Document stuff."""
|
|
89
|
+
try:
|
|
90
|
+
return getattr(self.doc, __name)
|
|
91
|
+
|
|
92
|
+
except Exception:
|
|
93
|
+
return object.__getattribute__(self, __name)
|
|
94
|
+
|
|
95
|
+
def add_page_numbers(self):
|
|
96
|
+
"""Add page numbers."""
|
|
97
|
+
add_page_number(self.doc.sections[0].footer.paragraphs[0])
|
|
98
|
+
|
|
99
|
+
def add_picture(self, fig, center=True, size=10, caption=None):
|
|
100
|
+
"""Add a picture in the document.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
----
|
|
104
|
+
doc: the document
|
|
105
|
+
fig : The matplotlib figure
|
|
106
|
+
center (bool, optional): If picture will be centerd. Defaults to True.
|
|
107
|
+
size (int, optional): Size of picture in cm. Defaults to 10.
|
|
108
|
+
caption (str, optional): The text of the caption. Defaults to None.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
Index of Figure if caption is True, otherwise -1.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
png_file = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
|
|
116
|
+
fig.savefig(png_file, dpi=300)
|
|
117
|
+
png_file.close()
|
|
118
|
+
P = self.add_paragraph()
|
|
119
|
+
if center:
|
|
120
|
+
P.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
121
|
+
|
|
122
|
+
P.add_run().add_picture(png_file.name, width=Cm(size))
|
|
123
|
+
rc = -1
|
|
124
|
+
if caption:
|
|
125
|
+
P = self.add_paragraph()
|
|
126
|
+
R = P.add_run("Figure ")
|
|
127
|
+
|
|
128
|
+
p = R.element # this is the actual lxml element for a paragraph
|
|
129
|
+
fld_xml = r"""<w:fldSimple %s w:instr="SEQ Figure \* ARABIC ">
|
|
130
|
+
<w:r><w:rPr>
|
|
131
|
+
<w:noProof/>
|
|
132
|
+
</w:rPr>
|
|
133
|
+
<w:t>%d</w:t>
|
|
134
|
+
</w:r></w:fldSimple>""" % (ns.nsdecls('w'), self.doc_fign)
|
|
135
|
+
rc = self.doc_fign
|
|
136
|
+
self.doc_fign += 1
|
|
137
|
+
|
|
138
|
+
# fld_xml = '<w:fldSimple %s w:instr=" SEQ Figure \* ARABIC "/>' % nsdecls('w')
|
|
139
|
+
fldSimple = parse_xml(fld_xml)
|
|
140
|
+
p.addnext(fldSimple)
|
|
141
|
+
P.add_run(". {}".format(caption))
|
|
142
|
+
P.style = self.styles['Caption']
|
|
143
|
+
if center:
|
|
144
|
+
P.alignment = paragraph_align_center()
|
|
145
|
+
|
|
146
|
+
os.remove(png_file.name)
|
|
147
|
+
return rc
|
|
148
|
+
|
|
149
|
+
def insert_table(self, rows=1, cols=1, caption=None, center=False):
|
|
150
|
+
"""Adds a table to the document.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
----
|
|
154
|
+
nrow: The number of rows in the table
|
|
155
|
+
ncol: The number of columns in the table
|
|
156
|
+
caption: The caption if not NOne. Defaults to None.
|
|
157
|
+
center: wheter to center the table and caption or not.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
The table object.
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
if caption:
|
|
165
|
+
P = self.add_paragraph()
|
|
166
|
+
R = P.add_run("Table ")
|
|
167
|
+
|
|
168
|
+
p = R.element # this is the actual lxml element for a paragraph
|
|
169
|
+
fld_xml = r"""<w:fldSimple %s w:instr="SEQ Table \* ARABIC ">
|
|
170
|
+
<w:r><w:rPr>
|
|
171
|
+
<w:noProof/>
|
|
172
|
+
</w:rPr>
|
|
173
|
+
<w:t>%d</w:t>
|
|
174
|
+
</w:r></w:fldSimple>""" % (ns.nsdecls('w'), self.doc_tbln)
|
|
175
|
+
self.doc_tbln += 1
|
|
176
|
+
|
|
177
|
+
# fld_xml = '<w:fldSimple %s w:instr=" SEQ Figure \* ARABIC "/>' % nsdecls('w')
|
|
178
|
+
fldSimple = parse_xml(fld_xml)
|
|
179
|
+
p.addnext(fldSimple)
|
|
180
|
+
P.add_run(". {}".format(caption))
|
|
181
|
+
P.style = self.styles['Caption']
|
|
182
|
+
if center:
|
|
183
|
+
P.alignment = paragraph_align_center()
|
|
184
|
+
|
|
185
|
+
table = self.add_table(rows=rows, cols=cols)
|
|
186
|
+
return table
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""A number of utils to make fits with numpy."""
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
|
+
from lmfit.models import GaussianModel
|
|
6
|
+
from scipy.signal import find_peaks
|
|
7
|
+
# from scipy.stats import moyal as landau
|
|
8
|
+
|
|
9
|
+
log2 = np.log(2)
|
|
10
|
+
s2pi = np.sqrt(2*np.pi)
|
|
11
|
+
spi = np.sqrt(np.pi)
|
|
12
|
+
s2 = np.sqrt(2.0)
|
|
13
|
+
tiny = np.finfo(np.float64).eps
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fit_gaussian(n, X, center, width=5.0, amplitude=1):
|
|
17
|
+
"""Fit a gaussion.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
----
|
|
21
|
+
n: The bins
|
|
22
|
+
X: the bin edges
|
|
23
|
+
center: The center (or mean) of the gaussian.
|
|
24
|
+
width: the sigma estimate of the gaussion. Defaults to 5.0.
|
|
25
|
+
amplitude: the estimae of the amplitude. Defaults to 1.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
the fit result and a legend
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
model = GaussianModel()
|
|
33
|
+
params = model.make_params(amplitude=amplitude, center=center, sigma=width)
|
|
34
|
+
result = model.fit(n, params, x=X)
|
|
35
|
+
legend = r'$\mu$=%.3f $\sigma$=%.3f' % (result.best_values['center'], result.best_values['sigma'])
|
|
36
|
+
return result, legend
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_multi_peak(peaks):
|
|
40
|
+
"""Create a multi gaussan model.
|
|
41
|
+
|
|
42
|
+
input is an array of (amplitude, center, width) tuples
|
|
43
|
+
"""
|
|
44
|
+
def create_single_peak(ipeak, center, width=5.0, amplitude=1):
|
|
45
|
+
"""Create a single gaussian with initial values as given.
|
|
46
|
+
|
|
47
|
+
Parameter:
|
|
48
|
+
ipeak - label for hte peak (a decimal number)
|
|
49
|
+
center - center of hte peak
|
|
50
|
+
width - width of the peak
|
|
51
|
+
amplitude - amplitude of the peak
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
pref = "f{0}_".format(ipeak)
|
|
55
|
+
model = GaussianModel(prefix=pref)
|
|
56
|
+
model.set_param_hint(pref+'amplitude', value=amplitude)
|
|
57
|
+
model.set_param_hint(pref+'center', value=center)
|
|
58
|
+
model.set_param_hint(pref+'sigma', value=width)
|
|
59
|
+
return model
|
|
60
|
+
|
|
61
|
+
ipeak = 0
|
|
62
|
+
mod = None
|
|
63
|
+
for ampl, center, sigma in peaks:
|
|
64
|
+
this_mod = create_single_peak(ipeak, center, sigma, ampl)
|
|
65
|
+
if mod is None:
|
|
66
|
+
mod = this_mod
|
|
67
|
+
else:
|
|
68
|
+
mod = mod + this_mod
|
|
69
|
+
|
|
70
|
+
ipeak += 1
|
|
71
|
+
|
|
72
|
+
return mod
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def fit_multi_gaus(hints, n, bins):
|
|
76
|
+
"""Fit a a number of gaussians as defined by the hints.
|
|
77
|
+
|
|
78
|
+
Parameter:
|
|
79
|
+
hints: an array of (ampl, mean, sigma)
|
|
80
|
+
n: histogram bin contents
|
|
81
|
+
bins: bin limits
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
result: The fit result object
|
|
86
|
+
out: an array of (mean, std) tuples, one per peak
|
|
87
|
+
legend: a lengend.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
width = (bins[1] - bins[0])
|
|
91
|
+
X = bins[:-1] + (0.5*width)
|
|
92
|
+
|
|
93
|
+
model = create_multi_peak(hints)
|
|
94
|
+
if model is None:
|
|
95
|
+
return None, None, None
|
|
96
|
+
|
|
97
|
+
# do the fit
|
|
98
|
+
result = model.fit(n, x=X)
|
|
99
|
+
legend = r""
|
|
100
|
+
out = []
|
|
101
|
+
for i in range(len(hints)):
|
|
102
|
+
pref = "f{0}_".format(i)
|
|
103
|
+
if i:
|
|
104
|
+
legend += '\n'
|
|
105
|
+
legend += r"$\mu$={:.3f} $\sigma$={:.3f}".format(result.best_values[pref + 'center'],
|
|
106
|
+
result.best_values[pref + 'sigma'])
|
|
107
|
+
|
|
108
|
+
out.append((result.best_values[pref + 'center'], result.best_values[pref + 'sigma']))
|
|
109
|
+
|
|
110
|
+
return result, out, legend
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def fit_peak_model(n, bins, distance=None, height=None, debug=None):
|
|
114
|
+
"""Fit a multigaussian model from the number of peaks found.
|
|
115
|
+
|
|
116
|
+
TODO: need to compute the distance from the noise and step
|
|
117
|
+
|
|
118
|
+
Parameter:
|
|
119
|
+
---------
|
|
120
|
+
n: histogram bin contents
|
|
121
|
+
bins: bin limits
|
|
122
|
+
distance: distance parameter find_peak
|
|
123
|
+
height: the cut on the peak height
|
|
124
|
+
debug: set to true to see extra information
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
result: The fit result object
|
|
129
|
+
out: an array of (mean, std) tuples, one per peak
|
|
130
|
+
legend: a lengend
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
width = (bins[1] - bins[0])
|
|
134
|
+
ntot = np.sum(n)
|
|
135
|
+
thrs = 0.01*ntot
|
|
136
|
+
if height is not None:
|
|
137
|
+
thrs = height
|
|
138
|
+
|
|
139
|
+
if debug:
|
|
140
|
+
print("ntot:", ntot)
|
|
141
|
+
print("thrs:", thrs)
|
|
142
|
+
print("width:", width)
|
|
143
|
+
|
|
144
|
+
peaks, prop = find_peaks(n, thrs, distance=distance)
|
|
145
|
+
if debug:
|
|
146
|
+
print("== Peaks")
|
|
147
|
+
for peak, ampl in zip(peaks, prop['peak_heights']):
|
|
148
|
+
print("\t height {:.3f} peak {:.3f} width {:.3f}".format(ampl, bins[peak], width))
|
|
149
|
+
|
|
150
|
+
hints = []
|
|
151
|
+
for peak, ampl in zip(peaks, prop['peak_heights']):
|
|
152
|
+
hints.append((ampl, bins[peak], width))
|
|
153
|
+
|
|
154
|
+
return fit_multi_gaus(hints, n, bins)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def draw_best_fit_orig(ax, result, bins, legend=None):
|
|
158
|
+
"""Draw the best fit."""
|
|
159
|
+
step = 0.5 * (bins[1] - bins[0])
|
|
160
|
+
X = bins[:-1] + step
|
|
161
|
+
ax.plot(X, result.best_fit)
|
|
162
|
+
if legend is not None:
|
|
163
|
+
ax.legend([legend], loc=1)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def draw_best_fit(ax, result, bins, npts=100, legend=None, color="#fa6e1e"):
|
|
167
|
+
"""Draw the best fit."""
|
|
168
|
+
X = np.linspace(bins[0], bins[:-1], num=npts)
|
|
169
|
+
Y = result.eval(param=result.params, x=X)
|
|
170
|
+
ax.plot(X, Y, color=color)
|
|
171
|
+
if legend is not None:
|
|
172
|
+
ax.legend([legend], loc=1)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
nevts = 10000
|
|
177
|
+
fig, ax = plt.subplots(nrows=1, ncols=2)
|
|
178
|
+
|
|
179
|
+
# Double peak gauss
|
|
180
|
+
peak1 = np.random.default_rng().normal(10.0, 0.5, nevts)
|
|
181
|
+
values = np.append(peak1, np.random.default_rng().normal(7.5, 0.75, int(0.75*nevts)))
|
|
182
|
+
|
|
183
|
+
count, bins, ignored = ax[0].hist(values, 50)
|
|
184
|
+
result, out, legend = fit_peak_model(count, bins, debug=True)
|
|
185
|
+
ax[0].legend([legend], loc=1)
|
|
186
|
+
draw_best_fit(ax[0], result, bins)
|
|
187
|
+
|
|
188
|
+
plt.show()
|