petal-qc 0.0.20__py3-none-any.whl → 0.0.22__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.

@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env python3
2
+ """Read Python table with reception tests."""
3
+
4
+ import sys
5
+ import math
6
+ import argparse
7
+ import pandas as pd
8
+ import json
9
+ from pathlib import Path
10
+
11
+ from itkdb_gtk import ITkDBlogin, ITkDButils
12
+
13
+ def check_value(value):
14
+ """Check that value is valid."""
15
+ if value is None:
16
+ return False
17
+
18
+ if isinstance(value, float):
19
+ if math.isnan(value):
20
+ return False
21
+
22
+ return True
23
+
24
+
25
+ def string_to_boolean(s):
26
+ """Converts a string to a boolean."""
27
+ val = s.lower()
28
+ if val=="pass" or val=="true" or val=="1":
29
+ return True
30
+ elif val=="fail" or val=="false" or val=="0":
31
+ return False
32
+ else:
33
+ raise ValueError
34
+
35
+ def parse_value(value):
36
+ """Parse value in cell."""
37
+ try:
38
+ spassed, comment = value.split(",")
39
+ except ValueError:
40
+ spassed = value
41
+ comment = ""
42
+
43
+ passed = string_to_boolean(spassed)
44
+ if passed is None:
45
+ return None, None
46
+
47
+ return passed, comment
48
+
49
+ def create_dto(session, SN, test_name):
50
+ """REturns the test DTO."""
51
+ user = ITkDButils.get_db_user(session)
52
+ defaults = {
53
+ "component": SN,
54
+ "institution": user["institutions"][0]["code"],
55
+ "runNumber": "1",
56
+ }
57
+ dto = ITkDButils.get_test_skeleton(session, "CORE_PETAL", test_name, defaults)
58
+ return dto
59
+
60
+ def do_visual_inspection(session, core, value):
61
+ """Uploads visual inspection."""
62
+ if not check_value(value):
63
+ return None
64
+
65
+ SN = core["serialNumber"]
66
+ dto = create_dto(session, SN, "VISUAL_INSPECTION")
67
+
68
+ passed, comment = parse_value(value)
69
+
70
+ if passed is None:
71
+ print("Wrong value fpr PASS/FAIL {} in {} for {}".format(value, "VISUAL_INSPECTION", core["alternativeIdentifier"]) )
72
+ return None
73
+
74
+ if passed:
75
+ dto["passed"] = True
76
+ else:
77
+ dto["passed"] = False
78
+ dto["defects"].append({
79
+ "name": "VISUAL",
80
+ "description": comment,
81
+ "properties": {}
82
+ })
83
+
84
+ return dto
85
+
86
+ def do_grounding(session, core, value):
87
+ """Uploads grounding check"""
88
+ if not check_value(value):
89
+ return None
90
+
91
+ SN = core["serialNumber"]
92
+ dto = create_dto(session, SN, "GROUNDING_CHECK")
93
+
94
+ fb, pipes, pipe_gnd = [float(x) for x in value.split(',')]
95
+ if fb > 2.0:
96
+ dto["passed"] = False
97
+ dto["defects"].append({
98
+ "name": "GROUND_FB",
99
+ "description": "resistance front-back is {} > 2 Ohm".format(fb),
100
+ "properties": {}
101
+ })
102
+
103
+ if pipes>0 and pipes<20.0e6:
104
+ dto["passed"] = False
105
+ dto["defects"].append({
106
+ "name": "GROUND_PIPES",
107
+ "description": "resistance between pipes is {} < 20 MOhm".format(pipes),
108
+ "properties": {}
109
+ })
110
+
111
+ if pipe_gnd>0 and pipe_gnd<20.0e6:
112
+ dto["passed"] = False
113
+ dto["defects"].append({
114
+ "name": "GROUND_PIPE_GND",
115
+ "description": "resistance between pipes and GNDis {} < 20 MOhm".format(pipe_gnd),
116
+ "properties": {}
117
+ })
118
+
119
+ dto["results"]["RESISTANCE_FB"] = fb
120
+ dto["results"]["RESISTANCE_PIPES"] = pipes
121
+ dto["results"]["RESISTANCE_PIPE_GND"] = pipe_gnd
122
+
123
+ return dto
124
+
125
+ def do_bending(session, core, value):
126
+ """Uploads bending."""
127
+ if not check_value(value):
128
+ return None
129
+
130
+ SN = core["serialNumber"]
131
+ dto = create_dto(session, SN, "BENDING120")
132
+ passed, comment = parse_value(value)
133
+ if passed is None:
134
+ print("Wrong value fpr PASS/FAIL {} in {} for {}".format(value, "BENDING120", core["alternativeIdentifier"]) )
135
+ return None
136
+
137
+ if passed:
138
+ dto["passed"] = True
139
+ else:
140
+ dto["passed"] = False
141
+ dto["defects"].append({
142
+ "name": "BENDING120",
143
+ "description": comment,
144
+ "properties": {}
145
+ })
146
+
147
+ return dto
148
+
149
+ def do_weight(session, core, value):
150
+ """Uploads weight."""
151
+ if not check_value(value):
152
+ return None
153
+
154
+ SN = core["serialNumber"]
155
+ dto = create_dto(session, SN, "PETAL_CORE_WEIGHT")
156
+
157
+ weight = float(value)
158
+ dto["results"]["WEIGHT"] = weight
159
+ if abs(weight-250)>25:
160
+ dto["passed"] = False
161
+ dto["defects"].append({
162
+ "name": "WEIGHT",
163
+ "description": "Petal core wights {:.1f} more than 25 gr. beyond 250.".format(weight),
164
+ "properties": {}
165
+ })
166
+ else:
167
+ dto["passed"] = True
168
+
169
+ return dto
170
+
171
+
172
+ def do_thickness(session, core, value):
173
+ """Uploads thickness."""
174
+ if not check_value(value):
175
+ return None
176
+
177
+ SN = core["serialNumber"]
178
+ dto = create_dto(session, SN, "CORE_THICKNESS")
179
+ thickness = float(value)
180
+ dto["results"]["THICKNESS"] = thickness
181
+
182
+ dto["passed"] = True
183
+ if abs(thickness-5.9)>0.25:
184
+ dto["problems"] = True
185
+ dto["comments"].append("Petal core wights {:.1f} more than 25 gr. beyond 250.".format(thickness))
186
+
187
+ return dto
188
+
189
+
190
+ def do_metrology_template(session, core, value):
191
+ """Uploads metrology template."""
192
+ if not check_value(value):
193
+ return None
194
+
195
+ SN = core["serialNumber"]
196
+ dto = create_dto(session, SN, "METROLOGY_TEMPLATE")
197
+ passed, comment = parse_value(value)
198
+ if passed is None:
199
+ print("Wrong value fpr PASS/FAIL {} in {} for {}".format(value, "TEMPLATE", core["alternativeIdentifier"]) )
200
+ return None
201
+
202
+ if passed:
203
+ dto["passed"] = True
204
+ else:
205
+ dto["passed"] = False
206
+ dto["defects"].append({
207
+ "name": "TEMPLATE",
208
+ "description": comment,
209
+ "properties": {}
210
+ })
211
+
212
+ return dto
213
+
214
+
215
+ def do_delamination(session, core, value):
216
+ """Uploads delamination."""
217
+ if not check_value(value):
218
+ return None
219
+
220
+ SN = core["serialNumber"]
221
+ dto = create_dto(session, SN, "DELAMINATION")
222
+
223
+ passed, comment = parse_value(value)
224
+ if passed is None:
225
+ print("Wrong value fpr PASS/FAIL {} in {} for {}".format(value, "DELAMINATION", core["alternativeIdentifier"]) )
226
+ return None
227
+
228
+ if passed:
229
+ dto["passed"] = True
230
+ else:
231
+ dto["passed"] = False
232
+ dto["defects"].append({
233
+ "name": "DELAMINATION",
234
+ "description": comment,
235
+ "properties": {}
236
+ })
237
+
238
+ return dto
239
+
240
+
241
+ def readTemplateTable(session, options):
242
+ """Main entry.
243
+
244
+ Args:
245
+ session (itkdb.Session): The PDB client.
246
+ options: program options
247
+ """
248
+
249
+ core_tests = {
250
+ "VISUAL_INSPECTION": do_visual_inspection,
251
+ "GROUNDING_CHECK": do_grounding,
252
+ "BENDING120": do_bending,
253
+ "PETAL_CORE_WEIGHT": do_weight,
254
+ "CORE_THICKNESS": do_thickness,
255
+ "METROLOGY_TEMPLATE": do_metrology_template,
256
+ "DELAMINATION": do_delamination
257
+ }
258
+ try:
259
+ sheet = int(options.sheet)
260
+ except ValueError:
261
+ sheet = options.sheet
262
+
263
+ df = pd.read_excel(options.files[0], sheet_name=sheet)
264
+ #print(df)
265
+
266
+ for row in df.itertuples():
267
+ petal_id = row.CORE_ID
268
+ if not check_value(petal_id):
269
+ break
270
+
271
+ print("\n\n### {}".format(petal_id))
272
+ core = ITkDButils.get_DB_component(session, petal_id)
273
+ for test, func in core_tests.items():
274
+ data = func(session, core, getattr(row, test))
275
+ if data is None:
276
+ continue
277
+
278
+ data["properties"]["OPERATOR"] = options.operator
279
+
280
+ #print(json.dumps(data, indent=3))
281
+ rc = ITkDButils.upload_test(session, data, check_runNumber=True)
282
+ if rc:
283
+ print("\n*** Could not upload test {} for {}".format(test, petal_id))
284
+ print(rc)
285
+ print()
286
+
287
+ def main():
288
+ """Entry point."""
289
+ parser = argparse.ArgumentParser()
290
+ parser.add_argument("files", nargs='*', help="The template spreadsheet")
291
+ parser.add_argument("--sheet", default="0", help="Sheet to read from excel file.")
292
+ parser.add_argument("--operator", default="Oihan Elesgaray", help="Name of operator.")
293
+
294
+ args = parser.parse_args()
295
+ if len(args.files) == 0:
296
+ print("I need an input file")
297
+ sys.exit()
298
+
299
+ if not Path(args.files[0]).exists():
300
+ print("Input file does not exist.")
301
+ sys.exit()
302
+
303
+
304
+ # ITk_PB authentication
305
+ dlg = ITkDBlogin.ITkDBlogin()
306
+ pdb_session = dlg.get_client()
307
+
308
+ readTemplateTable(pdb_session, args)
309
+
310
+ dlg.die()
311
+
312
+ if __name__ == "__main__":
313
+ main()
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- """Analyze the table generated by createMetrologyTable."""
2
+ """Analyze the table generated by createMetrologyTable.
3
+
4
+ It should also work with the table created by analyzeMetrologyTable, which is
5
+ the new default.
6
+ """
3
7
 
4
8
  import sys
5
9
  from pathlib import Path
@@ -13,7 +17,22 @@ from lmfit.models import LinearModel
13
17
 
14
18
  from petal_qc.utils.fit_utils import draw_best_fit
15
19
 
20
+ fig_width = 12.0
21
+ fig_height = 1.2*fig_width/3.0
22
+
23
+
24
+
16
25
  def distance(P1, P2, P3):
26
+ """Distance of P3 to line defined by P1-P2.
27
+
28
+ Args:
29
+ P1: Point 1 of the line
30
+ P2: Point 2 of the line
31
+ P3: Actual point
32
+
33
+ Returns:
34
+ float: distance
35
+ """
17
36
  C = np.cross(P2-P1, P3-P1)
18
37
  D = C/norm(P2-P1)
19
38
  return D
@@ -38,14 +57,41 @@ def remove_outliers_indx(data, cut=2.0, debug=False):
38
57
  indx = np.where(s < cut)[0]
39
58
  return indx
40
59
 
60
+ def draw_delta_scatter(ax, x, y, title, x_lim=None, y_lim=None, radius=None):
41
61
 
42
- def main(options):
43
- """main entry."""
44
- fig_width = 12.0
45
- fig_height = 1.2*fig_width/3.0
62
+ if x_lim is None:
63
+ x_lim=(-150, 150)
46
64
 
47
- T = pd.read_csv(options.files[0])
65
+ if y_lim is None:
66
+ y_lim=(-150, 150)
67
+
68
+ if radius is None:
69
+ radius = (25, 100)
70
+
71
+ ax.set_title(title)
72
+ ax.set_aspect('equal', adjustable='box')
73
+ ax.set_xlim(x_lim)
74
+ ax.set_ylim(y_lim)
75
+ circle = plt.Circle((0,0), radius[1], color="red", alpha=0.25)
76
+ ax.add_patch(circle)
77
+ circle = plt.Circle((0,0), radius[0], color="green", alpha=0.25)
78
+ ax.add_patch(circle)
48
79
 
80
+ ax.set_xlabel("X (µm)")
81
+ ax.set_ylabel("Y (µm)")
82
+ ax.grid()
83
+
84
+ ax.scatter(x, y, marker='.')
85
+
86
+
87
+ def analyze_fiducials(options, T):
88
+ """Analyze relative position of locator fiducials.
89
+
90
+ Args:
91
+ options (_type_): Program options
92
+ T (DataFrame): Pandas data frame.
93
+ """
94
+ side = T["side"].values[0]
49
95
  if options.mould > 0:
50
96
  x = 1000*T.loc[T['mould'] == options.mould, 'fd_dx'].values
51
97
  y = 1000*T.loc[T['mould'] == options.mould, 'fd_dy'].values
@@ -54,22 +100,23 @@ def main(options):
54
100
  x = 1000*T['fd_dx'].values
55
101
  y = 1000*T['fd_dy'].values
56
102
 
57
- fig, ax = plt.subplots(nrows=1, ncols=3, tight_layout=True, figsize=(fig_width, fig_height))
58
- fig.suptitle("Relative Position FD01-FD02")
59
- ax[0].set_title("FD01-FD02")
60
- ax[0].set_aspect('equal', adjustable='box')
61
- ax[0].set_xlim(-150, 150)
62
- ax[0].set_ylim(-150, 150)
63
- circle = plt.Circle((0,0), 75, color="red", alpha=0.25)
64
- ax[0].add_patch(circle)
65
- circle = plt.Circle((0,0), 25, color="green", alpha=0.25)
66
- ax[0].add_patch(circle)
67
-
68
- ax[0].set_xlabel("X (µm)")
69
- ax[0].set_ylabel("Y (µm)")
70
- ax[0].grid()
71
-
72
- ax[0].scatter(x, y, marker='.')
103
+ fig, ax = plt.subplots(nrows=1, ncols=2, tight_layout=True, figsize=(fig_width, fig_height))
104
+ fig.suptitle("Relative Position FD01-FD02 [{}]".format(side))
105
+ draw_delta_scatter(ax[0], x, y, "FD01-FD02")
106
+ # ax[0].set_title("FD01-FD02")
107
+ # ax[0].set_aspect('equal', adjustable='box')
108
+ # ax[0].set_xlim(-150, 150)
109
+ # ax[0].set_ylim(-150, 150)
110
+ # circle = plt.Circle((0,0), 100, color="red", alpha=0.25)
111
+ # ax[0].add_patch(circle)
112
+ # circle = plt.Circle((0,0), 25, color="green", alpha=0.25)
113
+ # ax[0].add_patch(circle)
114
+ #
115
+ # ax[0].set_xlabel("X (µm)")
116
+ # ax[0].set_ylabel("Y (µm)")
117
+ # ax[0].grid()
118
+ #
119
+ # ax[0].scatter(x, y, marker='.')
73
120
 
74
121
  model = LinearModel()
75
122
  params = model.guess(y, x=x)
@@ -94,15 +141,94 @@ def main(options):
94
141
  angle = 180*math.atan( result.best_values['slope'])/math.pi
95
142
  print("angle {:.5f} deg.".format(angle))
96
143
 
97
- ax[1].set_xlim(-150, 150)
98
- ax[1].set_xlabel("X (µm)")
144
+ ax[1].set_xlim(-50, 50)
145
+ ax[1].set_xlabel("distance to line (µm)")
99
146
  ax[1].grid()
100
- ax[1].hist(x)
147
+ ax[1].hist(values)
148
+
149
+ # ax[2].set_xlim(-75, 75)
150
+ # ax[2].set_xlabel("delta_X (µm)")
151
+ # ax[2].grid()
152
+ # ax[2].hist(x)
153
+
154
+ # ax[3].set_xlim(-150, 150)
155
+ # ax[3].set_xlabel("delta_Y (µm)")
156
+ # ax[3].grid()
157
+ # ax[3].hist(y)
158
+
159
+ def analyze_delta_pos(options, T):
160
+ """ Study dependency of delta pos in PL01-FD01 with the other locators.
161
+
162
+ Args:
163
+ options (_type_): Program options
164
+ T (DataFrame): Pandas data frame.
165
+ """
166
+ side = T["side"].values[0]
167
+ x = T["pl01_dx"].values
168
+ y = T["pl01_dy"].values
169
+ delta_pl01 = 1000*np.column_stack((x, y))
170
+ norm = np.array([ np.linalg.norm(P) for P in delta_pl01])
171
+ indx = np.where(norm<75.0)
172
+
173
+ delta_pl02 = 1000*np.column_stack((T["pl02_dx"].values, T["pl02_dy"].values))
174
+ delta_pl03 = 1000*np.column_stack((T["pl03_dx"].values, T["pl03_dy"].values))
175
+
176
+ fig, ax = plt.subplots(nrows=1, ncols=2, tight_layout=True, figsize=(fig_width, fig_height))
177
+ fig.suptitle("Delta from PL01 [{}]".format(side))
178
+ draw_delta_scatter(ax[0], delta_pl02[indx, 0], delta_pl02[indx, 1], "PL02")
179
+ draw_delta_scatter(ax[1], delta_pl03[indx, 0], delta_pl03[indx, 1], "PL02")
180
+
181
+
182
+ def analyze_parallelism(options, T):
183
+ """Analyze parallelism.
184
+
185
+ Args:
186
+ options (_type_): Program options
187
+ T (DataFrame): Pandas data frame.
188
+ """
189
+ if options.institute is None:
190
+ df = T
191
+ else:
192
+ df = T.loc[T["institute"] == options.institute]
193
+
194
+
195
+ side = df["side"].values[0]
196
+ labels = []
197
+ for L in df["petal_id"].values:
198
+ ival = int(L[5:])
199
+ labels.append(ival)
200
+
201
+ x = 1000*df["flatness"].values
202
+ y = 1000*df["parallelism"].values
203
+
204
+ fig, ax = plt.subplots(nrows=1, ncols=1, tight_layout=True)
205
+ fig.suptitle("Parallelism .vs. flatness [{}]".format(side))
206
+
207
+ ax.scatter(x, y, marker='.')
208
+ for xp, yp, lbl in zip(x, y, labels):
209
+ ax.text(xp, yp, "{}".format(lbl))
210
+
211
+ ax.set_xlabel("flatness (µm)")
212
+ ax.set_ylabel("parallelism (µm)")
213
+ ax.grid()
214
+
215
+ fig, ax = plt.subplots(nrows=1, ncols=1, tight_layout=True)
216
+ fig.suptitle("Offset [{}]".format(side))
217
+ ax.hist(1000*T["offset"].values)
218
+
219
+
220
+ def main(options):
221
+ """main entry."""
222
+ T = pd.read_csv(options.files[0])
223
+
224
+ # analyze fiducials
225
+ analyze_fiducials(options, T)
226
+
227
+ # Understand deviation from nominal.
228
+ analyze_delta_pos(options, T)
101
229
 
102
- ax[2].set_xlim(-150, 150)
103
- ax[2].set_xlabel("Y (µm)")
104
- ax[2].grid()
105
- ax[2].hist(y)
230
+ # Correlate planarity y parallelism
231
+ analyze_parallelism(options, T)
106
232
 
107
233
  plt.show()
108
234
 
@@ -110,6 +236,9 @@ if __name__ == "__main__":
110
236
  parser = argparse.ArgumentParser()
111
237
  parser.add_argument('files', nargs='*', help="Input files")
112
238
  parser.add_argument('--mould', default=-1, type=int, help="mould index")
239
+ parser.add_argument("--institute",
240
+ default=None,
241
+ help="Either IFIC or DESY to treat the different files")
113
242
 
114
243
  opts = parser.parse_args()
115
244
  if len(opts.files) == 0:
@@ -323,10 +323,10 @@ if __name__ == "__main__":
323
323
  parser.add_argument("--cores", dest="cores", action=RangeListAction, default=[],
324
324
  help="Create list of cores to analyze. The list is made with numbers or ranges (ch1:ch2 or ch1:ch2:step) ")
325
325
 
326
-
326
+ opts = parser.parse_args()
327
327
  dlg = ITkDBlogin.ITkDBlogin()
328
328
  session = dlg.get_client()
329
329
 
330
- main(session, options)
330
+ main(session, opts)
331
331
 
332
332
  dlg.die()
@@ -38,15 +38,17 @@ def main(options):
38
38
  data = json.load(fin)
39
39
 
40
40
  if not data["passed"]:
41
- petalId = data["component"]
42
- if petalId is None:
41
+ if data["component"] is None:
43
42
  print("Petal {} has bad Serial number".format(petal_id))
44
- if petal_id not in petal_cores:
45
- petal_cores[petal_id] = {"FRONT": [], "BACK": []}
43
+ continue
44
+
45
+ petalId = "{}-{}".format(petal_id, data["component"])
46
+ if petalId not in petal_cores:
47
+ petal_cores[petalId] = {"FRONT": [], "BACK": []}
46
48
 
47
49
  side = "FRONT" if "FRONT" in data["testType"] else "BACK"
48
50
  for D in data["defects"]:
49
- petal_cores[petal_id][side].append("{}: {}".format(D["name"], D["description"]))
51
+ petal_cores[petalId][side].append("{}: {}".format(D["name"], D["description"]))
50
52
 
51
53
 
52
54
  keys = sorted(petal_cores.keys())
@@ -69,8 +71,8 @@ if __name__ == "__main__":
69
71
  opts = parser.parse_args()
70
72
 
71
73
 
72
- folder = Path("/tmp/petal-metrology/results")
73
- # folder = Path("~/tmp/petal-metrology/Production/Results").expanduser()
74
+ #folder = Path("/tmp/petal-metrology/results")
75
+ folder = Path("~/tmp/petal-metrology/Production/Results").expanduser()
74
76
  opts.files = []
75
77
  for fnam in all_files(folder, "*.json"):
76
78
  opts.files.append(fnam)
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python
2
+ """Test."""
3
+
4
+ from pathlib import Path
5
+ from petal_qc.metrology import DataFile
6
+
7
+ ifile = Path("/tmp/petals/re-measured/test-xx.txt")
8
+
9
+ data = DataFile.read(ifile, r"Punto(-|(Vision-))\d", "Punto")
10
+ print(data)
@@ -51,6 +51,7 @@ class IRDataGetter(object):
51
51
 
52
52
  def fine_tune_params(self, param):
53
53
  """Set default values for the parameters."""
54
+ param.sensor_max = -5
54
55
  return
55
56
 
56
57
  def get_IR_data(self, image, **kargs):
@@ -298,6 +299,7 @@ class IRDataDESY(IRDataGetter):
298
299
  """Set default values for the parameters."""
299
300
  param.distance = 16
300
301
  param.width = 16
302
+ param.sensor_max = -5
301
303
 
302
304
  def get_IR_data(self, image, **kargs):
303
305
  """Get the data from the image in the proper orientation.
@@ -515,7 +515,7 @@ def find_slice_minimum(T, X):
515
515
  return Tmin, Pmin
516
516
 
517
517
 
518
- def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=7, debug=False):
518
+ def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=6, debug=False):
519
519
  """Get the temperature profile between A and B.
520
520
 
521
521
  Args:
@@ -569,13 +569,16 @@ def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=7, debug=False):
569
569
 
570
570
  # Do the polinomialfit.
571
571
  if ndof > npdim:
572
- c, stats = Polynomial.polyfit(X, T, npdim, full=True)
572
+ indx = remove_outliers(np.array(T), 6, indx=True)
573
+ TT = np.array(T)
574
+ XX = np.array(X)
575
+ c, stats = Polynomial.polyfit(XX[indx], TT[indx], npdim, full=True)
573
576
  pfunc = Polynomial.Polynomial(c)
574
577
 
575
578
  # Get the polinomial derivative roots and curvature.
576
579
  # This is done to catch double minimums
577
580
 
578
- # valid_roots, valid_values, valid_curv = get_derivatives(T_min, X[1], X[-1], c)
581
+ valid_roots, valid_values, valid_curv = get_derivatives(T_min, X[1], X[-1], c)
579
582
 
580
583
  #
581
584
  # TODO: temporary remove the search for double minima in the segment.
@@ -585,6 +588,13 @@ def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=7, debug=False):
585
588
  print("--- Multi-minimum segment.")
586
589
  for r, v, crv in zip(valid_roots, valid_values, valid_curv):
587
590
  print("{} -> {} [{}]".format(r, v, crv))
591
+
592
+ x = np.linspace(x_min, x_max, 3*npts)
593
+ debug_plot.plot('TProf', X, T, 'o', x, pfunc(x), '-', valid_roots, valid_values, 'o')
594
+ if len(valid_roots) > 1:
595
+ debug_plot.setup_debug('TProfMulti')
596
+ debug_plot.plot('TProfMulti', X, T, 'o', x, pfunc(x), '-', valid_roots, valid_values, 'o')
597
+ debug_plot.set_title('TProfMulti', "Curvature: {}".format(str(valid_curv)))
588
598
 
589
599
  # Find the polynomial minimum with `minimize` (redundant, probably)
590
600
  X0 = X[np.argmin(T)]
@@ -722,7 +732,7 @@ def get_derivatives(T_min, x_min, x_max, coeff):
722
732
  valid_values = pfunc(valid_roots)
723
733
  valid_curv = deriv2(valid_roots)
724
734
 
725
- indx = np.where(valid_curv > 0.1)
735
+ indx = np.where(valid_curv > 1e-3)
726
736
  valid_roots = valid_roots[indx]
727
737
  valid_values = valid_values[indx]
728
738
  valid_curv = valid_curv[indx]
@@ -983,13 +993,14 @@ def get_IR_pipe(data, params):
983
993
  return min_path
984
994
 
985
995
 
986
- def remove_outliers(data, cut=2.0, debug=False):
996
+ def remove_outliers(data, cut=2.0, indx=False, debug=False):
987
997
  """Remove points far away form the rest.
988
998
 
989
999
  Args:
990
1000
  ----
991
1001
  data : The data
992
1002
  cut: max allowed distance
1003
+ indx: if True return indices rather than a new array
993
1004
  debug: be bverbose if True
994
1005
 
995
1006
  """
@@ -997,7 +1008,10 @@ def remove_outliers(data, cut=2.0, debug=False):
997
1008
  d = np.abs(data - np.median(data))
998
1009
  mdev = np.median(d)
999
1010
  s = d / (mdev if mdev else 1.)
1000
- return data[s < cut]
1011
+ if indx:
1012
+ return np.where(s < cut)[0]
1013
+ else:
1014
+ return data[s < cut]
1001
1015
 
1002
1016
 
1003
1017
  def get_T_along_path(irpath, data, width, norm=True):