petal-qc 0.0.14.dev1__py3-none-any.whl → 0.0.16__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,77 @@
1
+ #!/usr/bin/env python3
2
+ """Locates all raw data files."""
3
+
4
+ import os
5
+ import sys
6
+ import fnmatch
7
+ import re
8
+ import shutil
9
+ from pathlib import Path
10
+ import argparse
11
+
12
+ def all_files(root, patterns='*', single_level=False, yield_folders=False):
13
+ """A generator that reruns all files in the given folder.
14
+
15
+ Args:
16
+ ----
17
+ root (file path): The folder
18
+ patterns (str, optional): The pattern of the files. Defaults to '*'.
19
+ single_level (bool, optional): If true, do not go into sub folders. Defaults to False.
20
+ yield_folders (bool, optional): If True, return folders as well. Defaults to False.
21
+
22
+ Yields
23
+ ------
24
+ str: file path name
25
+
26
+ """
27
+ patterns = patterns.split(';')
28
+ for path, subdirs, files in os.walk(root):
29
+ if yield_folders:
30
+ files.extend(subdirs)
31
+
32
+ files.sort()
33
+ for name in files:
34
+ for pattern in patterns:
35
+ if fnmatch.fnmatch(name, pattern):
36
+ yield os.path.join(path, name)
37
+ break
38
+
39
+ if single_level:
40
+ break
41
+
42
+ def main(options):
43
+ """Locate raw data files in input folder and copies them in out folder
44
+
45
+ Args:
46
+ folder_in (Path): Input folder
47
+ folder_oyt (Path): Output folder
48
+ """
49
+ reg = re.compile(r"(PPC\.[0-9]+)-(\w+)")
50
+ outF = Path(options.output).expanduser().resolve()
51
+ with open(outF, "w", encoding="utf-8") as fout:
52
+ for folder_in in options.files:
53
+ inF = Path(folder_in).expanduser().resolve()
54
+ if not inF.exists():
55
+ print("Input folder does not exist. {}".format(inF))
56
+ continue
57
+
58
+ for fnam in all_files(inF, "PPC*.txt", yield_folders=False):
59
+ R = reg.search(fnam)
60
+ if R is None:
61
+ continue
62
+
63
+ petal_id = R.group(1)
64
+ side = R.group(2)
65
+ fout.write("{ifile} {pid}-{side} {pid}\n".format(ifile=fnam, side=side, pid=petal_id))
66
+
67
+
68
+ if __name__ == "__main__":
69
+ parser = argparse.ArgumentParser()
70
+ parser.add_argument('files', nargs='*', help="Input files")
71
+ parser.add_argument("--output", dest="output", help="Output file", default="out.txt")
72
+ opts = parser.parse_args()
73
+ if len(opts.files)==0:
74
+ print("I need at least one input folder")
75
+ sys.exit(-1)
76
+
77
+ main(opts)
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python3
2
+ """Create table for metrology analysis."""
3
+
4
+ import sys
5
+ import re
6
+ import json
7
+ from pathlib import Path
8
+ from argparse import ArgumentParser
9
+ from petal_qc.utils.all_files import all_files
10
+ from petal_qc.utils.ArgParserUtils import RangeListAction
11
+
12
+ r_petal_id = re.compile("PPC.([0-9]*)")
13
+
14
+ db_file = Path("/Users/lacasta/cernbox/workspace/Petal-QC/PetalMould.csv")
15
+
16
+ petal_db = {}
17
+ with open(db_file, "r", encoding="utf-8") as fin:
18
+ for i, line in enumerate(fin):
19
+ if i==0:
20
+ continue
21
+
22
+ values = [x.strip() for x in line.split(",")]
23
+ petal_db[values[0]] = values[1:]
24
+
25
+ def main(options):
26
+ """Main entry."""
27
+ ifolder = Path(options.input)
28
+ if not ifolder.exists():
29
+ print("Input folder does not exist.\n{}".format(ifolder))
30
+ return
31
+
32
+ has_list = len(options.cores) != 0
33
+
34
+ ofile = open(options.out, "w", encoding="utf-8")
35
+ ofile.write("PetalID,SN,side,mould,fd_dx,fd_dy,paralelism,R0,R1,R2,R3S0,R3S1,R4S0,R4S1,R5S0,R5S1,flatness\n")
36
+
37
+ for fnam in all_files(ifolder.as_posix(), "*.json"):
38
+ fstem = fnam.stem
39
+ if "PPC." not in fstem:
40
+ continue
41
+
42
+ R = r_petal_id.search(fstem)
43
+ if R is None:
44
+ continue
45
+
46
+ petal_id = R.group(0)
47
+ pid = int(R.group(1))
48
+
49
+ if has_list and pid not in options.cores:
50
+ continue
51
+
52
+ SN = petal_db[petal_id][0]
53
+ side = 1 if "back" in fstem else 0
54
+ mould = int(petal_db[petal_id][1])
55
+
56
+ data = None
57
+ with open(fnam, "r", encoding="utf-8") as fin:
58
+ data = json.load(fin)
59
+
60
+ ofile.write("{}, {}, {}, {}".format(petal_id, SN, side, mould))
61
+ print("data SN", data["component"])
62
+ D = data["results"]["METROLOGY"]["REL_POS_DELTA"]["FD01-FD02"]
63
+ ofile.write(",{:.5f}, {:.5f}".format(D[0], D[1]))
64
+
65
+ D = data["results"]["METROLOGY"]["PARALLELISM"]
66
+ ofile.write(",{:.5f}".format(D))
67
+
68
+ for x in data["results"]["METROLOGY"]["FLATNESS_LOCAL"]:
69
+ ofile.write(", {:.5f}".format(x))
70
+
71
+ D = data["results"]["METROLOGY"]["FLATNESS_GLOBAL"]
72
+ ofile.write(", {:.5f}".format(D))
73
+
74
+ ofile.write("\n")
75
+
76
+ ofile.close()
77
+
78
+
79
+ if __name__ == "__main__":
80
+ parser = ArgumentParser()
81
+ parser.add_argument("--input-folder", dest="input", default=".", help="Input folder")
82
+ parser.add_argument("--out", default="out.csv", help="Output table.")
83
+ parser.add_argument("--cores", dest="cores", action=RangeListAction, default=[],
84
+ help="Create list of cores to analyze. The list is made with numbers or ranges (ch1:ch2 or ch1:ch2:step) ")
85
+
86
+ opts = parser.parse_args()
87
+ main(opts)
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env python3
2
+ """Get module bow from desy metrology files"""
3
+ import sys
4
+ import re
5
+ from pathlib import Path
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ import numpy.linalg as linalg
9
+
10
+
11
+ from petal_qc.utils.all_files import all_files
12
+ from petal_qc.metrology import DataFile
13
+ from petal_qc.metrology import DataFile
14
+ from petal_qc.utils.Geometry import fit_plane
15
+ from petal_qc.utils.Geometry import project_to_plane
16
+ from petal_qc.utils.Geometry import remove_outliers_indx
17
+
18
+
19
+ rgx = "serialNo =\\s+([A-Z0-9]+)"
20
+ serial_no = re.compile(rgx)
21
+
22
+ mtype = re.compile("Project Name:\\s+([A-Za-z0-9]+)_bow")
23
+
24
+
25
+ def get_array_range(A):
26
+ """Gets mean and range of given array."""
27
+ avg = np.mean(A)
28
+ imin = np.argmin(A)
29
+ vmin = A[imin]
30
+ imax = np.argmax(A)
31
+ vmax = A[imax]
32
+ stdv = np.std(A)
33
+ return avg, stdv, [vmin, vmax], [imin, imax]
34
+
35
+
36
+
37
+ def create_bow_figure(SN, mtype, pout, width) -> plt.Figure:
38
+ """Create the sensor bow figure.
39
+
40
+ Args:
41
+ options: Program options.
42
+ pout: set of points
43
+ width: the actual bow.
44
+
45
+ Returns:
46
+ plt.Figure: The bow figure.
47
+ """
48
+ fig_bow, ax = plt.subplots(subplot_kw={'projection': '3d'})
49
+ fig_bow.suptitle(r"{}_{} - Sensor bow {:.1f} $\mu$m".format(SN, mtype, width))
50
+
51
+ zplt = 1000*pout[:, 2]
52
+ # surf = ax.plot_trisurf(pout[:, 0], pout[:, 1], zplt, cmap=plt.cm.jet, edgecolor="black", linewidths=0.2)
53
+ surf = ax.scatter(pout[:, 0], pout[:, 1], zplt, c=zplt, marker="o", edgecolor='none')
54
+ ax.set_xlabel("X")
55
+ ax.set_ylabel("Y")
56
+ ax.set_zlabel("Z")
57
+ cbar = fig_bow.colorbar(surf, shrink=0.5, aspect=5, location="left")
58
+ cbar.set_label(r"Z ($\mu$m)")
59
+ return fig_bow
60
+
61
+
62
+ def module_bow(ifile):
63
+ """Compute module bow."""
64
+ SN = None
65
+ mod_type = None
66
+ print(Path(ifile).name)
67
+ with open(ifile, "r", encoding="utf-8") as fin:
68
+ ss = fin.read()
69
+ mtx = serial_no.search(ss)
70
+ if mtx:
71
+ SN = mtx.group(1)
72
+
73
+ mtp = mtype.search(ss)
74
+ if mtp:
75
+ mod_type = mtp.group(1)
76
+
77
+ if SN is None:
78
+ return None, None, None
79
+
80
+ print("{} - {}".format(SN, mod_type))
81
+
82
+ data = DataFile.read(ifile, "(Bow|Sensor)")
83
+ if len(data) == 0:
84
+ return None, None, None
85
+
86
+ # Try to remove locator points and get the plane
87
+ indx = remove_outliers_indx(data[:, 2])
88
+ M, TM, *_ = fit_plane(data[indx], use_average=False)
89
+
90
+ # project all data to the plane
91
+ Zmean = np.mean(M[:, 2])
92
+ M[:, 2] -= Zmean
93
+
94
+ mean, stdev, rng, irng = get_array_range(M[:, 2])
95
+ width = 1000*(rng[1]-rng[0])
96
+
97
+ # Check which one is closest to the center
98
+ min2center = linalg.norm(M[irng[0], 0:2])
99
+ max2center = linalg.norm(M[irng[1], 0:2])
100
+ # If center above the top, bow negative
101
+ if max2center < min2center:
102
+ width = -width
103
+
104
+ print("Sensor bow\nAverage: {:.1f} min {:.1f} max {:.1f} rng {:.1f}\n".format(
105
+ mean, 1000*rng[0], 1000*rng[1], width))
106
+
107
+ fig_bow = create_bow_figure(SN, mod_type, M, width)
108
+ fig_bow.savefig("{}-{}-bow.png".format(SN, mod_type), dpi=300)
109
+ plt.close(fig_bow)
110
+ del fig_bow
111
+ return SN, mod_type, width
112
+
113
+
114
+ def main(folder):
115
+ """Main entry"""
116
+ fout = open("module-bow.txt", "w", encoding="utf-8")
117
+ for fnam in all_files(folder, "*.txt"):
118
+ SN, mod_type, width = module_bow(fnam)
119
+ if SN:
120
+ fout.write("{}-{} : {:.3f}\n".format(SN, mod_type, width))
121
+
122
+ fout.close()
123
+
124
+ if __name__ == "__main__":
125
+ main("/Users/lacasta/Downloads/InterposerPetal")
126
+ #main("/Users/lacasta/Downloads/kkdvk")
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python3
2
+ """Locates all raw data files."""
3
+
4
+ import os
5
+ import sys
6
+ import fnmatch
7
+ import re
8
+ import shutil
9
+ from pathlib import Path
10
+ import argparse
11
+
12
+ def all_files(root, patterns='*', single_level=False, yield_folders=False):
13
+ """A generator that reruns all files in the given folder.
14
+
15
+ Args:
16
+ ----
17
+ root (file path): The folder
18
+ patterns (str, optional): The pattern of the files. Defaults to '*'.
19
+ single_level (bool, optional): If true, do not go into sub folders. Defaults to False.
20
+ yield_folders (bool, optional): If True, return folders as well. Defaults to False.
21
+
22
+ Yields
23
+ ------
24
+ str: file path name
25
+
26
+ """
27
+ patterns = patterns.split(';')
28
+ for path, subdirs, files in os.walk(root):
29
+ if yield_folders:
30
+ files.extend(subdirs)
31
+
32
+ files.sort()
33
+ for name in files:
34
+ for pattern in patterns:
35
+ if fnmatch.fnmatch(name, pattern):
36
+ yield os.path.join(path, name)
37
+ break
38
+
39
+ if single_level:
40
+ break
41
+
42
+ def main(folder_in, folder_out):
43
+ """Locate raw data files in input folder and copies them in out folder
44
+
45
+ Args:
46
+ folder_in (Path): Input folder
47
+ folder_oyt (Path): Output folder
48
+ """
49
+ inF = Path(folder_in).expanduser().resolve()
50
+ if not inF.exists():
51
+ print("Input folder does not exist. {}".format(inF))
52
+ return
53
+
54
+ reg = re.compile(r"(PPC\.[0-9]+).*metr")
55
+
56
+ outF = Path(folder_out).expanduser().resolve()
57
+ if not outF.exists():
58
+ os.makedirs(outF.as_posix())
59
+
60
+ list_file = open("{}/raw-data-metrology-cores.txt".format(outF.as_posix()), "w", encoding="UTF-8")
61
+ for fnam in all_files(inF, "PPC*.txt", yield_folders=False):
62
+ R = reg.search(fnam)
63
+ if R is None:
64
+ continue
65
+
66
+ petal_id = R.group(1)
67
+ if "back" in fnam:
68
+ side = "{}-back".format(petal_id)
69
+ ofile = "{}-back.txt".format(petal_id)
70
+ elif "front" in fnam:
71
+ side = "{}-front".format(petal_id)
72
+ ofile = "{}-front.txt".format(petal_id)
73
+ else:
74
+ print("Invalid file {}".format(fnam))
75
+ continue
76
+
77
+ out_name = outF / ofile
78
+ list_file.write("{} {} {}\n".format(out_name.as_posix(), side, petal_id))
79
+ shutil.copy(fnam, out_name)
80
+
81
+ list_file.close()
82
+
83
+ if __name__ == "__main__":
84
+ parser = argparse.ArgumentParser()
85
+ parser.add_argument("--input-folder", dest="input", default=None, help="Input folder")
86
+ parser.add_argument("--output-folder", dest="output", help="Outout fodler", default=None)
87
+ opts = parser.parse_args()
88
+ if opts.input is None or opts.output is None:
89
+ print("I need both an input and an output folder.")
90
+ sys.exit(-1)
91
+
92
+ main(opts.input, opts.output)
@@ -200,6 +200,7 @@ def main(session):
200
200
  for T in core_tests:
201
201
  counter[T]=0
202
202
 
203
+ mould_db = {}
203
204
  for core in core_list:
204
205
  SN = core["serialNumber"]
205
206
  altid = core['alternativeIdentifier']
@@ -243,6 +244,7 @@ def main(session):
243
244
  mould_desc = do_manufacturing(good_tests["MANUFACTURING"])
244
245
  pos = mould_desc.rfind('.')
245
246
  mould_id = mould_desc[pos+1:]
247
+ mould_db[altid] = (SN, mould_id)
246
248
  if mould_id not in Mould_values:
247
249
  Mould_values[mould_id] = {}
248
250
 
@@ -302,6 +304,12 @@ def main(session):
302
304
  plot_weighing(weights, petal_ids, document)
303
305
  plot_metrology(M_values, Mould_values, petal_ids, document)
304
306
  document.save("AVStests.docx")
307
+
308
+ with open("mould_db.csv", "w", encoding="utf-8") as fout:
309
+ fout.write("PetalID, SerialNo, MouldID\n")
310
+ for key, val in mould_db.items():
311
+ fout.write("{}, {}, {}\n".format(key, val[0], val[1]))
312
+
305
313
  plt.show()
306
314
 
307
315
  if __name__ == "__main__":
@@ -4,6 +4,7 @@ import os
4
4
  import sys
5
5
  import fnmatch
6
6
  import re
7
+ import shutil
7
8
  from pathlib import Path
8
9
  import argparse
9
10
 
@@ -52,14 +53,23 @@ def main(folder, out_folder):
52
53
  folder: the input folder where to find the original files.
53
54
  out_folder: the folder where the new fiels will be stored.
54
55
  """
56
+ inF = Path(folder).expanduser().resolve()
57
+ if not inF.exists():
58
+ print("Input folder does not exist. {}".format(inF))
59
+ return
60
+
61
+ print("Reading folder: {}".format(inF))
62
+ print("output in {}".format(out_folder))
55
63
  outF = Path(out_folder).expanduser().resolve()
56
64
  if not outF.exists():
65
+ print("creating {}".format(outF))
57
66
  os.mkdir(outF)
58
67
 
59
68
  rgx = re.compile(r"Project Name: (\w+)side_.*AlternativeID=PPC[-_](\d+)", re.MULTILINE|re.DOTALL)
60
69
  petal_cores = {}
61
- for fnam in all_files(folder, "*.txt"):
70
+ for fnam in all_files(inF, "*.txt"):
62
71
  P = Path(fnam).expanduser().resolve()
72
+ print(P.name)
63
73
  with open(fnam, "r", encoding="UTF-8") as ff:
64
74
  R = rgx.search(ff.read())
65
75
  if R:
@@ -86,7 +96,8 @@ def main(folder, out_folder):
86
96
  data = ""
87
97
  for data_type, fnam in values.items():
88
98
  if fnam is None:
89
- print("This should not happen.")
99
+ print("Missing file name for {} {} [{}]".format(petal_id, side, data_type))
100
+ continue
90
101
 
91
102
  with open(fnam, "r", encoding="UTF-8") as ifile:
92
103
  data += ifile.read()
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env python3
2
+ """List failing cores from JSon files."""
3
+ import sys
4
+ import argparse
5
+ from pathlib import Path
6
+ import json
7
+
8
+
9
+
10
+ def main(options):
11
+ """main entry."""
12
+ petal_cores = {}
13
+ for fnam in options.files:
14
+ ifile = Path(fnam).expanduser().resolve()
15
+ with open(ifile, "r", encoding="utf-8") as fin:
16
+ data = json.load(fin)
17
+
18
+ if not data["passed"]:
19
+ petalId = data["component"]
20
+ if petalId not in petal_cores:
21
+ petal_cores[petalId] = {"FRONT": [], "BACK": []}
22
+
23
+ side = "FRONT" if "FRONT" in data["testType"] else "BACK"
24
+ for D in data["defects"]:
25
+ petal_cores[petalId][side].append("{}: {}".format(D["name"], D["description"]))
26
+
27
+
28
+ keys = sorted(petal_cores.keys())
29
+ for petalId in keys:
30
+ print(petalId)
31
+ for side in ["FRONT","BACK"]:
32
+ if len(petal_cores[petalId][side])>0:
33
+ print("+-", side)
34
+ for D in petal_cores[petalId][side]:
35
+ print(" ", D)
36
+
37
+ print("\n")
38
+
39
+
40
+ if __name__ == "__main__":
41
+ parser = argparse.ArgumentParser()
42
+ parser.add_argument('files', nargs='*', help="Input files")
43
+ opts = parser.parse_args()
44
+
45
+ from petal_qc.utils.all_files import all_files
46
+
47
+ opts.files = []
48
+ for fnam in all_files(Path("~/tmp/petal-metrology/Production/Results").expanduser(), "*.json"):
49
+ opts.files.append(fnam)
50
+
51
+ main(opts)
@@ -24,21 +24,12 @@ from petal_qc.thermal import contours
24
24
  from petal_qc.thermal import IRBFile
25
25
  from petal_qc.thermal import IRPetal
26
26
  from petal_qc.thermal import PipeFit
27
- from petal_qc.thermal.Petal_IR_Analysis import AnalysisResult
28
27
  from petal_qc.thermal.Petal_IR_Analysis import show_2D_image
29
28
  from petal_qc.thermal.PetalColorMaps import HighContrast
30
29
 
31
30
  import petal_qc.utils.Progress as Progress
32
31
  import petal_qc.utils.utils as utils
33
-
34
-
35
- class CommaSeparatedListAction(Action):
36
- """Create a list from the comma sepparated numbers at imput."""
37
-
38
- def __call__(self, parser, namespace, values, option_string=None):
39
- """The actual action."""
40
- setattr(namespace, self.dest, list(map(int, values.split(','))))
41
-
32
+ from petal_qc.utils.ArgParserUtils import CommaSeparatedListAction
42
33
 
43
34
  def get_min_max(values, step=1.0):
44
35
  """Return min and max.
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env python3
2
+ """A number of utilities for argument parser."""
3
+
4
+ from argparse import Action
5
+
6
+ class CommaSeparatedListAction(Action):
7
+ """Create a list from the comma sepparated numbers at imput."""
8
+
9
+ def __call__(self, parser, namespace, values, option_string=None):
10
+ """The actual action."""
11
+ setattr(namespace, self.dest, list(map(int, values.split(','))))
12
+
13
+
14
+ class RangeListAction(Action):
15
+ """Create a list from a range expresion at input.
16
+
17
+ The list is made with numbers or ranges (ch1:ch2 or ch1:ch2:step)
18
+ """
19
+
20
+ def __call__(self, parser, namespace, values, option_string=None):
21
+ """The actual action."""
22
+ value = []
23
+ for V in values.split(','):
24
+ try:
25
+ value.append(int(V))
26
+ except ValueError:
27
+ if ':' not in V:
28
+ continue
29
+
30
+ items = V.split(':')
31
+ if len(items)==1:
32
+ continue
33
+
34
+ ival = list(map(int, items))
35
+ ival[1] += 1
36
+ for x in range(*ival):
37
+ value.append(int(x))
38
+
39
+
40
+ setattr(namespace, self.dest, value)
41
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: petal_qc
3
- Version: 0.0.14.dev1
3
+ Version: 0.0.16
4
4
  Summary: A collection of scripts for Petal CORE QC.
5
5
  Author-email: Carlos Lacasta <carlos.lacasta@cern.ch>
6
6
  Project-URL: Homepage, https://gitlab.cern.ch/atlas-itk/sw/db/itk-pdb-gtk-gui-utils
@@ -10,14 +10,14 @@ Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.7
11
11
  Description-Content-Type: text/markdown
12
12
  Requires-Dist: itkdb
13
- Requires-Dist: itkdb-gtk >=0.10.10
13
+ Requires-Dist: itkdb_gtk>=0.10.10
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: matplotlib
16
16
  Requires-Dist: lmfit
17
17
  Requires-Dist: openpyxl
18
18
  Requires-Dist: pandas
19
- Requires-Dist: python-dateutil
20
- Requires-Dist: python-docx
19
+ Requires-Dist: python_dateutil
20
+ Requires-Dist: python_docx
21
21
  Requires-Dist: scipy
22
22
  Requires-Dist: scikit-image
23
23