itkdb-gtk 0.9.0__py3-none-any.whl → 0.10.0.dev2__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 itkdb-gtk might be problematic. Click here for more details.
- itkdb_gtk/CreateShipments.py +41 -55
- itkdb_gtk/GetShipments.py +4 -21
- itkdb_gtk/GlueWeight.py +2 -2
- itkdb_gtk/ITkDButils.py +25 -13
- itkdb_gtk/PanelVisualInspection.py +230 -0
- itkdb_gtk/{GroundVITests.py → PetalReceptionTests.py} +7 -6
- itkdb_gtk/SensorUtils.py +490 -0
- itkdb_gtk/UploadModuleIV.py +206 -497
- itkdb_gtk/UploadMultipleTests.py +2 -2
- itkdb_gtk/UploadPetalInformation.py +164 -94
- itkdb_gtk/UploadTest.py +6 -4
- itkdb_gtk/__init__.py +5 -3
- itkdb_gtk/dashBoard.py +13 -12
- itkdb_gtk/dbGtkUtils.py +93 -5
- itkdb_gtk/readAVSdata.py +204 -72
- {itkdb_gtk-0.9.0.dist-info → itkdb_gtk-0.10.0.dev2.dist-info}/METADATA +1 -1
- itkdb_gtk-0.10.0.dev2.dist-info/RECORD +29 -0
- {itkdb_gtk-0.9.0.dist-info → itkdb_gtk-0.10.0.dev2.dist-info}/entry_points.txt +2 -2
- itkdb_gtk-0.9.0.dist-info/RECORD +0 -27
- {itkdb_gtk-0.9.0.dist-info → itkdb_gtk-0.10.0.dev2.dist-info}/WHEEL +0 -0
- {itkdb_gtk-0.9.0.dist-info → itkdb_gtk-0.10.0.dev2.dist-info}/top_level.txt +0 -0
itkdb_gtk/UploadModuleIV.py
CHANGED
|
@@ -10,277 +10,61 @@ webApp aqui:
|
|
|
10
10
|
https://itk-pdb-webapps-strips.web.cern.ch
|
|
11
11
|
|
|
12
12
|
"""
|
|
13
|
+
import sys
|
|
13
14
|
import os
|
|
14
15
|
import json
|
|
15
|
-
import
|
|
16
|
+
import tempfile
|
|
17
|
+
import copy
|
|
16
18
|
from pathlib import Path
|
|
17
19
|
import shutil
|
|
18
20
|
|
|
19
|
-
import gi
|
|
20
|
-
import tempfile
|
|
21
|
-
|
|
22
|
-
gi.require_version("Gtk", "3.0")
|
|
23
|
-
from gi.repository import Gtk, Gio
|
|
24
|
-
|
|
25
|
-
|
|
26
21
|
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
|
|
27
22
|
from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3 as NavigationToolbar
|
|
28
23
|
import numpy as np
|
|
29
24
|
import matplotlib as mpl
|
|
30
25
|
import matplotlib.pyplot as plt
|
|
31
26
|
|
|
27
|
+
import gi
|
|
28
|
+
|
|
29
|
+
gi.require_version("Gtk", "3.0")
|
|
30
|
+
from gi.repository import Gtk, Gio
|
|
31
|
+
|
|
32
32
|
try:
|
|
33
33
|
import itkdb_gtk
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
except ImportError:
|
|
36
|
-
|
|
37
|
-
from pathlib import Path
|
|
38
|
-
cwd = Path(sys.argv[0]).parent.parent
|
|
36
|
+
cwd = Path(__file__).parent.parent
|
|
39
37
|
sys.path.append(cwd.as_posix())
|
|
40
38
|
|
|
41
|
-
from itkdb_gtk import dbGtkUtils, ITkDBlogin, ITkDButils, UploadTest
|
|
39
|
+
from itkdb_gtk import dbGtkUtils, ITkDBlogin, ITkDButils, UploadTest, SensorUtils
|
|
42
40
|
|
|
43
41
|
# Check if Gtk can be open
|
|
44
42
|
gtk_runs, gtk_args = Gtk.init_check()
|
|
45
43
|
|
|
46
44
|
|
|
47
45
|
def remove_files(W, flist):
|
|
48
|
-
|
|
49
|
-
os.unlink(f)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
#
|
|
53
|
-
# The following is taken from
|
|
54
|
-
# https://gitlab.cern.ch/atlas-itk/sw/db/production_database_scripts.git
|
|
55
|
-
#
|
|
56
|
-
mm = 1e-3
|
|
57
|
-
cm = 1e-2
|
|
58
|
-
UpperV = 500 # For sensor to pass, I must be < Imax up until this voltage and no breakdown must be detected before then.
|
|
59
|
-
StabilityV = 700 # Voltage with multiple current readings to check stability
|
|
60
|
-
|
|
61
|
-
AreaDict = {
|
|
62
|
-
"Unknown": (97.621 - 0.450) * (97.950 - 0.550) * mm * mm,
|
|
63
|
-
#
|
|
64
|
-
"ATLAS12": 95.7 * 95.64 * mm * mm,
|
|
65
|
-
"ATLAS17LS": (97.621 - 0.450) * (97.950 - 0.550) * mm * mm,
|
|
66
|
-
#
|
|
67
|
-
"ATLAS18R0": 89.9031 * cm * cm,
|
|
68
|
-
"ATLAS18R1": 89.0575 * cm * cm,
|
|
69
|
-
"ATLAS18R2": 74.1855 * cm * cm,
|
|
70
|
-
"ATLAS18R3": 80.1679 * cm * cm,
|
|
71
|
-
"ATLAS18R4": 87.4507 * cm * cm,
|
|
72
|
-
"ATLAS18R5": 91.1268 * cm * cm,
|
|
73
|
-
#
|
|
74
|
-
"ATLAS18SS": 93.6269 * cm * cm,
|
|
75
|
-
"ATLAS18LS": 93.6269 * cm * cm,
|
|
76
|
-
#
|
|
77
|
-
"ATLASDUMMY18": (97.621 - 0.450) * (97.950 - 0.550) * mm * mm,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def LocateMicroDischarge(
|
|
82
|
-
I,
|
|
83
|
-
V,
|
|
84
|
-
sm_window=2,
|
|
85
|
-
bd_limit=5.5,
|
|
86
|
-
allow_running_bd=True,
|
|
87
|
-
use_additional_cond=False,
|
|
88
|
-
tolerence=0.05,
|
|
89
|
-
voltage_span=4,
|
|
90
|
-
fit_window=5,
|
|
91
|
-
):
|
|
92
|
-
"""
|
|
93
|
-
Function for BDV estimation - if questions please contact Vera Latonova (vera.latonova@cern.ch).
|
|
94
|
-
I,V must have same shape and voltages must be in ascending order,
|
|
95
|
-
same indexes of I&V arrays must correspond each other,
|
|
96
|
-
only invalid data or holdstep should be stripped before
|
|
97
|
-
but it is not necessary. Measurments U=0&I=0 are removed.
|
|
98
|
-
If there is same or higher amount of same voltages in row than
|
|
99
|
-
sm_window, from this sequence we cannot estimete dI/dV and
|
|
100
|
-
we have to remove this averaged point.
|
|
101
|
-
|
|
102
|
-
It is assumed that only parameter use_additional_cond would be
|
|
103
|
-
changed by caller. Changing of other parameters may affect
|
|
104
|
-
BDV unexpectedly.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@param[in] I - array of currents without any cut
|
|
108
|
-
@param[in] V - array of voltages, ascending order, without any cut
|
|
109
|
-
@param[in] sm_window - size of smoothing window
|
|
110
|
-
@param[in] bd_limit - BD limit for |U| < 500V
|
|
111
|
-
@param[in] allow_running_bd - allow increase bd_limit for |U| > 500
|
|
112
|
-
@param[in] use_additional_cond - use additional BD contition
|
|
113
|
-
@param[in] tolerence - configuration of additional condition
|
|
114
|
-
@param[in] voltage_span - max width of hump on spectra which may be neglected
|
|
115
|
-
in voltage steps in additional contition
|
|
116
|
-
@param[in] fit_window - number of points used for linear fit before BD voltage
|
|
117
|
-
|
|
118
|
-
@return BD voltage (always positive) or NO_BD_CONST = 9.99e99 if not found.
|
|
119
|
-
"""
|
|
120
|
-
NO_BD_CONST = 9.99e99
|
|
121
|
-
|
|
122
|
-
# add nan to the end of array
|
|
123
|
-
V = np.abs(V)
|
|
124
|
-
I = np.abs(I)
|
|
125
|
-
|
|
126
|
-
# skip zeros
|
|
127
|
-
ind = np.where(np.logical_or(I != 0, V != 0))
|
|
128
|
-
V = V[ind]
|
|
129
|
-
I = I[ind]
|
|
130
|
-
|
|
131
|
-
V_ = np.append(V, np.nan * np.ones(sm_window - 1))
|
|
132
|
-
I_ = np.append(I, np.nan * np.ones(sm_window - 1))
|
|
133
|
-
|
|
134
|
-
# make 2D array of I's, V's each row_ind shifted by row_ind index
|
|
135
|
-
# i.e from array [1,3,5] we make (for sm_window=2) 2D array
|
|
136
|
-
# [ 1,3,5,nan]
|
|
137
|
-
# [nan,5,1,3]
|
|
138
|
-
# than get average from each column -> I_avg, V_avg
|
|
139
|
-
r = np.arange(sm_window)
|
|
140
|
-
|
|
141
|
-
V2 = np.outer(np.ones(sm_window), V_)
|
|
142
|
-
row_ind, col_ind = np.ogrid[: V2.shape[0], : V2.shape[1]]
|
|
143
|
-
col_ind = col_ind - r[:, np.newaxis]
|
|
144
|
-
V2 = V2[row_ind, col_ind]
|
|
145
|
-
# strip fields with nans
|
|
146
|
-
V2 = np.transpose(V2[:, (sm_window - 1) : -(sm_window - 1)])
|
|
147
|
-
|
|
148
|
-
I2 = np.outer(np.ones(sm_window), I_)
|
|
149
|
-
row_ind, col_ind = np.ogrid[: I2.shape[0], : I2.shape[1]]
|
|
150
|
-
col_ind = col_ind - r[:, np.newaxis]
|
|
151
|
-
I2 = I2[row_ind, col_ind]
|
|
152
|
-
I2 = np.transpose(I2[:, (sm_window - 1) : -(sm_window - 1)])
|
|
153
|
-
|
|
154
|
-
# get V & I averages
|
|
155
|
-
try:
|
|
156
|
-
V_avg = np.average(V2, axis=1)
|
|
157
|
-
I_avg = np.average(I2, axis=1)
|
|
158
|
-
except ZeroDivisionError:
|
|
159
|
-
# not enough data
|
|
160
|
-
return NO_BD_CONST
|
|
161
|
-
|
|
162
|
-
# find dI / dV array
|
|
163
|
-
# I'm not able to write this without cycle
|
|
164
|
-
dIdV = np.array([])
|
|
165
|
-
for i in range(V2.shape[0]):
|
|
166
|
-
with warnings.catch_warnings():
|
|
167
|
-
warnings.filterwarnings("error")
|
|
168
|
-
try:
|
|
169
|
-
dIdV = np.append(dIdV, np.polyfit(V2[i, :], I2[i, :], 1)[0])
|
|
170
|
-
except (np.RankWarning, TypeError):
|
|
171
|
-
dIdV = np.append(dIdV, np.nan)
|
|
172
|
-
|
|
173
|
-
# stripping U[n] == U[n+1] (i.e. hodlsetp) => fit cannot be sucessful =>
|
|
174
|
-
# dIdV is nan @holdstep
|
|
175
|
-
ind = np.where(np.isfinite(dIdV))
|
|
176
|
-
I_avg = I_avg[ind]
|
|
177
|
-
V_avg = V_avg[ind]
|
|
178
|
-
dIdV = dIdV[ind]
|
|
179
|
-
|
|
180
|
-
# get running BDV limit & compare
|
|
181
|
-
bd_limit_running = bd_limit + np.where(
|
|
182
|
-
allow_running_bd and V_avg > 500, (V_avg - 500.0) / 100.0, 0
|
|
183
|
-
)
|
|
184
|
-
V_avg_BD_ind = dIdV / (I_avg / V_avg) > bd_limit_running
|
|
185
|
-
V_avg_BD = V_avg[V_avg_BD_ind]
|
|
186
|
-
|
|
187
|
-
# Estimate BDV
|
|
188
|
-
BDV = np.array([])
|
|
189
|
-
|
|
190
|
-
# no break-down
|
|
191
|
-
if V_avg_BD.shape == (0,):
|
|
192
|
-
return NO_BD_CONST
|
|
193
|
-
|
|
194
|
-
# if V_avg_BD_ind[0] == True ... BDV <- V[0]
|
|
195
|
-
# for others V_avg_BD_ind[n] == True BDV <- (V_avg[n] + V_avg[n-1])/2
|
|
196
|
-
if V_avg_BD_ind[0]:
|
|
197
|
-
BDV = np.append(BDV, V[0])
|
|
198
|
-
V_avg_BD_ind[0] = False
|
|
199
|
-
|
|
200
|
-
BDV = np.append(
|
|
201
|
-
BDV,
|
|
202
|
-
(V_avg[np.where(V_avg_BD_ind)] + V_avg[np.where(V_avg_BD_ind)[0] - 1]) / 2.0,
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
###########################################################################
|
|
206
|
-
## Application of additional condition ####################################
|
|
207
|
-
###########################################################################
|
|
208
|
-
if not use_additional_cond:
|
|
209
|
-
return BDV[0]
|
|
210
|
-
|
|
211
|
-
# get index if V <= BDV
|
|
212
|
-
B = np.where(np.less.outer(BDV, V))
|
|
213
|
-
col_ind = np.mgrid[: BDV.shape[0], : V.shape[0]][1]
|
|
214
|
-
col_ind[B[0], B[1]] = 0
|
|
215
|
-
V_BDV_ind = np.max(col_ind, axis=1)
|
|
216
|
-
|
|
217
|
-
back_ok_v_ind = 0
|
|
218
|
-
while True:
|
|
219
|
-
with warnings.catch_warnings():
|
|
220
|
-
warnings.filterwarnings("error")
|
|
221
|
-
try:
|
|
222
|
-
a, b = np.polyfit(
|
|
223
|
-
V[
|
|
224
|
-
max(back_ok_v_ind, V_BDV_ind[0] - fit_window) : max(
|
|
225
|
-
back_ok_v_ind, V_BDV_ind[0]
|
|
226
|
-
)
|
|
227
|
-
],
|
|
228
|
-
I[
|
|
229
|
-
max(back_ok_v_ind, V_BDV_ind[0] - fit_window) : max(
|
|
230
|
-
back_ok_v_ind, V_BDV_ind[0]
|
|
231
|
-
)
|
|
232
|
-
],
|
|
233
|
-
1,
|
|
234
|
-
)
|
|
235
|
-
except (np.RankWarning, TypeError):
|
|
236
|
-
return BDV[0]
|
|
237
|
-
|
|
238
|
-
ind = np.where(1 - (a * V + b) / I <= tolerence)[0]
|
|
239
|
-
try:
|
|
240
|
-
back_ok_v_ind = np.min(ind[ind > V_BDV_ind[0] + 1])
|
|
241
|
-
except ValueError:
|
|
242
|
-
# sensor is not going back
|
|
243
|
-
return BDV[0]
|
|
244
|
-
# hump is too long -- it cannot be skipped
|
|
245
|
-
if back_ok_v_ind - V_BDV_ind[0] > voltage_span:
|
|
246
|
-
return BDV[0]
|
|
247
|
-
|
|
248
|
-
# skip BDVs inside hump
|
|
249
|
-
ind = BDV >= V[back_ok_v_ind]
|
|
250
|
-
BDV = BDV[ind]
|
|
251
|
-
V_BDV_ind = V_BDV_ind[ind]
|
|
252
|
-
if V_avg_BD.shape == (0,):
|
|
253
|
-
return NO_BD_CONST
|
|
254
|
-
return NO_BD_CONST
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def scale_iv(I, T1, T2):
|
|
258
|
-
"""Normalize corrent to given temperature (T2)
|
|
46
|
+
"""Remove files given in the input list.
|
|
259
47
|
|
|
260
48
|
Args:
|
|
261
|
-
|
|
262
|
-
T1 (float): Original temperature
|
|
263
|
-
T2 (float): New temperature.
|
|
264
|
-
|
|
265
|
-
Return:
|
|
266
|
-
Array with scaled currents.
|
|
49
|
+
flist (list): list of filenames.
|
|
267
50
|
|
|
268
51
|
"""
|
|
269
|
-
|
|
270
|
-
|
|
52
|
+
for f in flist:
|
|
53
|
+
os.unlink(f)
|
|
271
54
|
|
|
272
55
|
|
|
273
56
|
class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
274
57
|
"""GUI for IV file handling."""
|
|
275
58
|
|
|
276
|
-
def __init__(self, session, title="IV window", options=None):
|
|
59
|
+
def __init__(self, session, title="IV window", options=None, help=None):
|
|
277
60
|
"""Initialization."""
|
|
278
61
|
super().__init__(
|
|
279
|
-
session=session, title=title, show_search=None, gtk_runs=gtk_runs
|
|
62
|
+
session=session, title=title, show_search=None, gtk_runs=gtk_runs, help=help
|
|
280
63
|
)
|
|
281
64
|
self.mdata = {}
|
|
282
65
|
self.mod_type = {}
|
|
283
66
|
self.mod_SN = {}
|
|
67
|
+
self.last_folder = None
|
|
284
68
|
self.difference = None
|
|
285
69
|
self.canvas = None
|
|
286
70
|
|
|
@@ -304,7 +88,7 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
304
88
|
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
|
|
305
89
|
button.add(image)
|
|
306
90
|
button.set_tooltip_text("Click to upload test")
|
|
307
|
-
button.connect("clicked", self.
|
|
91
|
+
button.connect("clicked", self.do_upload)
|
|
308
92
|
self.hb.pack_end(button)
|
|
309
93
|
|
|
310
94
|
# File entry and search button
|
|
@@ -330,12 +114,12 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
330
114
|
grid.attach(self.double_file, 1, 2, 1, 1)
|
|
331
115
|
grid.attach(self.double_SN, 2, 2, 1, 1)
|
|
332
116
|
|
|
333
|
-
btn = Gtk.Button(label="Compute difference")
|
|
334
|
-
btn.connect("clicked", self.on_difference)
|
|
335
|
-
grid.attach(btn, 1, 3, 1, 1)
|
|
117
|
+
#btn = Gtk.Button(label="Compute difference")
|
|
118
|
+
#btn.connect("clicked", self.on_difference)
|
|
119
|
+
#grid.attach(btn, 1, 3, 1, 1)
|
|
336
120
|
|
|
337
121
|
btn = Gtk.Button(label="Upload to DB")
|
|
338
|
-
btn.connect("clicked", self.
|
|
122
|
+
btn.connect("clicked", self.do_upload)
|
|
339
123
|
grid.attach(btn, 2, 3, 1, 1)
|
|
340
124
|
|
|
341
125
|
self.mainBox.pack_start(grid, False, True, 0)
|
|
@@ -358,7 +142,7 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
358
142
|
toolbar = NavigationToolbar(self.canvas)
|
|
359
143
|
except TypeError:
|
|
360
144
|
toolbar = NavigationToolbar(self.canvas, self)
|
|
361
|
-
|
|
145
|
+
|
|
362
146
|
self.mainBox.pack_start(toolbar, False, False, 0)
|
|
363
147
|
|
|
364
148
|
# The text view
|
|
@@ -366,184 +150,88 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
366
150
|
|
|
367
151
|
self.show_all()
|
|
368
152
|
|
|
369
|
-
def
|
|
370
|
-
"""
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
except KeyError:
|
|
374
|
-
dbGtkUtils.complain("Cannot Upload to DB", "Data not yet ready.")
|
|
375
|
-
return
|
|
153
|
+
def get_difference_data(self):
|
|
154
|
+
"""Returns the double data witht the difference."""
|
|
155
|
+
# Prepare the data
|
|
156
|
+
ddata = copy.deepcopy(self.mdata["double"])
|
|
376
157
|
|
|
377
|
-
|
|
378
|
-
|
|
158
|
+
ndata = len(self.difference)
|
|
159
|
+
ddata["curve"]["V"] = np.abs(ddata["curve"]["V"][:ndata])
|
|
160
|
+
ddata["curve"]["I"] = np.abs(self.difference)
|
|
161
|
+
ddata["curve"]["S"] = ddata["curve"]["S"][:ndata]
|
|
162
|
+
return ddata
|
|
379
163
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
self.session, "MODULE", self.mdata["double"]["TestType"]
|
|
383
|
-
)
|
|
384
|
-
tp = "ATLAS18{}".format(self.mod_type["double"][0:2])
|
|
385
|
-
area = AreaDict[tp] / cm**2
|
|
164
|
+
def upload_test(self, mdata, mod_type):
|
|
165
|
+
"""Upload available tests."""
|
|
386
166
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
self.session, "SENSOR", self.mdata["double"]["TestType"]
|
|
390
|
-
)
|
|
391
|
-
area = AreaDict[self.mod_type["double"]] / cm**2
|
|
392
|
-
|
|
393
|
-
# The data arrays
|
|
394
|
-
V = np.abs(mdata["curve"]["V"])
|
|
395
|
-
I = np.abs(mdata["curve"]["I"])
|
|
396
|
-
passed = True
|
|
397
|
-
|
|
398
|
-
# Find Current @ 500V
|
|
399
|
-
indx = np.where(V == 500)[0]
|
|
400
|
-
i_500 = I[indx][0] / area
|
|
401
|
-
self.write_message("I @ 500V = {:.2f} nA/cm2\n".format(i_500))
|
|
402
|
-
|
|
403
|
-
# Compute current stability
|
|
404
|
-
IStability = abs(I[abs(V) == StabilityV])
|
|
405
|
-
IVariation = -1
|
|
406
|
-
if np.size(IStability) > 1: # Maybe make == 4?
|
|
407
|
-
IVariation = abs(np.std(IStability) / np.mean(IStability))
|
|
408
|
-
|
|
409
|
-
self.write_message("I stability = {:.6f} nA\n".format(IVariation))
|
|
410
|
-
|
|
411
|
-
# Search for Micro discharges
|
|
412
|
-
# Check for micro-discharge in non-normalized current,
|
|
413
|
-
# removing duplicate Voltage entries (e.g. for stability measurements)
|
|
414
|
-
comments = []
|
|
415
|
-
defects = []
|
|
416
|
-
UniqueVs, UniqueIndices = np.unique(V, return_index=True)
|
|
417
|
-
MicroDischargeV = LocateMicroDischarge(I[UniqueIndices], UniqueVs)
|
|
418
|
-
if MicroDischargeV < np.max(V):
|
|
419
|
-
comments.append("Found micro discharge: {:.1f} V\n".format(MicroDischargeV))
|
|
420
|
-
self.write_message(comments[-1])
|
|
421
|
-
|
|
422
|
-
if MicroDischargeV < UpperV:
|
|
423
|
-
txt = "microdischarge happening before {:.1f}V.".format(UpperV)
|
|
424
|
-
defects.append({
|
|
425
|
-
"name": "MicroDischarge",
|
|
426
|
-
"description": txt,
|
|
427
|
-
"properties": {}
|
|
428
|
-
}
|
|
429
|
-
)
|
|
430
|
-
self.write_message("...{}. FAILED\n".format(txt))
|
|
431
|
-
passed = False
|
|
432
|
-
|
|
433
|
-
test["component"] = self.mod_SN["double"]
|
|
434
|
-
test["institution"] = mdata["Institute"]
|
|
435
|
-
test["runNumber"] = mdata["RunNumber"]
|
|
436
|
-
test["date"] = ITkDButils.get_db_date(
|
|
437
|
-
"{} {}".format(mdata["Date"], mdata["Time"])
|
|
438
|
-
)
|
|
439
|
-
test["passed"] = passed
|
|
440
|
-
test["problems"] = False
|
|
441
|
-
test["properties"]["VBIAS_SMU"] = mdata["Vbias_SMU"]
|
|
442
|
-
test["properties"]["RSERIES"] = mdata["Rseries"]
|
|
443
|
-
test["properties"]["TEST_DMM"] = mdata["Test_DMM"]
|
|
444
|
-
test["properties"]["RSHUNT"] = mdata["Rshunt"]
|
|
445
|
-
test["properties"]["RUNNUMBER"] = mdata["RunNumber"]
|
|
446
|
-
test["properties"]["COMMENTS"] = mdata["Comments"]
|
|
447
|
-
test["properties"]["ALGORITHM_VERSION"] = "0.0.0"
|
|
448
|
-
if is_module:
|
|
449
|
-
test["properties"]["SOFTWARE_TYPE_VERSION"] = "pyProbe"
|
|
450
|
-
test["properties"]["MODULE_STAGE"] = mdata["Module_Stage"]
|
|
451
|
-
|
|
452
|
-
test["results"]["TEMPERATURE"] = mdata["Temperature"]
|
|
453
|
-
test["results"]["HUMIDITY"] = mdata["Humidity"]
|
|
454
|
-
test["results"]["VBD"] = MicroDischargeV
|
|
455
|
-
test["results"]["I_500V"] = i_500
|
|
456
|
-
test["results"]["VOLTAGE"] = -np.abs(V)
|
|
457
|
-
test["results"]["CURRENT"] = -np.abs(I)
|
|
458
|
-
test["results"]["RMS_STABILITY"] = IVariation
|
|
459
|
-
test["results"]["SHUNT_VOLTAGE"] = np.zeros(V.shape)
|
|
460
|
-
test["defects"] = defects
|
|
461
|
-
test["comments"] = comments
|
|
167
|
+
# Get JSon skeleton filled
|
|
168
|
+
test = SensorUtils.sensor_data_to_json(self.session, mdata, mod_type, self)
|
|
462
169
|
|
|
463
170
|
# write attachment.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
"Type",
|
|
467
|
-
"Wafer",
|
|
468
|
-
"Module_SN",
|
|
469
|
-
"Module_Stage",
|
|
470
|
-
"Date",
|
|
471
|
-
"Time",
|
|
472
|
-
"Institute",
|
|
473
|
-
"TestType",
|
|
474
|
-
"Vbias_SMU",
|
|
475
|
-
"Rseries",
|
|
476
|
-
"Test_DMM",
|
|
477
|
-
"Rshunt",
|
|
478
|
-
"Software type and version, fw version",
|
|
479
|
-
"RunNumber",
|
|
480
|
-
"Temperature",
|
|
481
|
-
"Humidity",
|
|
482
|
-
"Comments",
|
|
483
|
-
]
|
|
484
|
-
else:
|
|
485
|
-
items = [
|
|
486
|
-
"Type",
|
|
487
|
-
"Batch",
|
|
488
|
-
"Wafer",
|
|
489
|
-
"Component",
|
|
490
|
-
"Date",
|
|
491
|
-
"Time",
|
|
492
|
-
"Institute",
|
|
493
|
-
"TestType",
|
|
494
|
-
"Vbias_SMU",
|
|
495
|
-
"Rseries",
|
|
496
|
-
"Test_DMM",
|
|
497
|
-
"Rshunt",
|
|
498
|
-
"RunNumber",
|
|
499
|
-
"Temperature",
|
|
500
|
-
"Humidity",
|
|
501
|
-
"Comments",
|
|
502
|
-
]
|
|
171
|
+
# First geet the fine name.
|
|
172
|
+
fnam = SensorUtils.build_file_name(mdata)
|
|
503
173
|
|
|
504
|
-
try:
|
|
505
|
-
fnam = "{}_{}_IV_{}-".format(
|
|
506
|
-
self.mod_SN["double"], mdata["Module_Stage"], mdata["RunNumber"]
|
|
507
|
-
)
|
|
508
|
-
except KeyError:
|
|
509
|
-
fnam = "{}_W_IV_{}".format(self.mod_SN["double"], mdata["RunNumber"])
|
|
510
174
|
|
|
511
|
-
data_out = tempfile.NamedTemporaryFile(
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
for key in items:
|
|
516
|
-
if key == "Module_SN" or key == "Component":
|
|
517
|
-
data_out.write("{}: {}\n".format(key, self.mod_SN["double"]))
|
|
518
|
-
else:
|
|
519
|
-
data_out.write("{}: {}\n".format(key, mdata[key]))
|
|
520
|
-
|
|
521
|
-
for il, label in enumerate(mdata["curve"]["labels"]):
|
|
522
|
-
if il:
|
|
523
|
-
data_out.write("\t")
|
|
524
|
-
data_out.write(label)
|
|
525
|
-
data_out.write("\n")
|
|
526
|
-
|
|
527
|
-
ncol = len(mdata["curve"]["labels"])
|
|
528
|
-
ndata = len(self.difference)
|
|
529
|
-
for i in range(ndata):
|
|
530
|
-
v = -abs(V[i])
|
|
531
|
-
iv = -abs(self.difference[i])
|
|
532
|
-
if ncol > 2:
|
|
533
|
-
data_out.write("{:.2f}\t{:.2f}\t{:.2f}\n".format(v, iv, 0.0))
|
|
534
|
-
else:
|
|
535
|
-
data_out.write("{:.2f}\t{:.2f}\n".format(v, iv))
|
|
536
|
-
|
|
537
|
-
print(data_out.name)
|
|
538
|
-
data_out.close()
|
|
539
|
-
|
|
540
|
-
js_out = tempfile.NamedTemporaryFile(
|
|
541
|
-
"w", prefix="payload-", suffix=".json", delete=False
|
|
542
|
-
)
|
|
175
|
+
data_out = tempfile.NamedTemporaryFile("w", prefix=fnam, suffix=".dat", delete=False)
|
|
176
|
+
SensorUtils.save_sensor_data(data_out, mdata, name=fnam)
|
|
177
|
+
|
|
178
|
+
js_out = tempfile.NamedTemporaryFile("w", prefix="payload-", suffix=".json", delete=False)
|
|
543
179
|
js_out.write(json.dumps(test, indent=3, cls=dbGtkUtils.MyEncoder))
|
|
544
180
|
js_out.close()
|
|
545
181
|
|
|
546
|
-
|
|
182
|
+
attachment = ITkDButils.Attachment(data_out.name, "resultsFile", fnam)
|
|
183
|
+
uploadW = UploadTest.UploadTest(self.session, js_out.name, attachment)
|
|
184
|
+
uploadW.connect("destroy", remove_files, [data_out.name, js_out.name])
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def do_upload(self, *args):
|
|
188
|
+
"""The upload button has been clicked.
|
|
189
|
+
|
|
190
|
+
We present a dialog where we ask if the new data file should be stored
|
|
191
|
+
locally and if both (single and difference) tests should be uploaded.
|
|
192
|
+
"""
|
|
193
|
+
if "single" not in self.mdata:
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
if "double" not in self.mdata:
|
|
197
|
+
# upload only the single test.
|
|
198
|
+
if dbGtkUtils.ask_for_confirmation(
|
|
199
|
+
"Uploading Single data",
|
|
200
|
+
"No data for double module/sensor.\nUpload single test ?."):
|
|
201
|
+
|
|
202
|
+
self.upload_test(self.mdata["single"], self.mod_type["single"])
|
|
203
|
+
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
# We create the dialog.
|
|
207
|
+
dlg = Gtk.Dialog(title="Add Attachment", parent=self, flags=0)
|
|
208
|
+
dlg.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
|
209
|
+
Gtk.STOCK_OK, Gtk.ResponseType.OK)
|
|
210
|
+
area = dlg.get_content_area()
|
|
211
|
+
grid = Gtk.Grid(column_spacing=5, row_spacing=1)
|
|
212
|
+
area.add(grid)
|
|
213
|
+
|
|
214
|
+
label = Gtk.Label(label="Save locally new data file ?")
|
|
215
|
+
save_locally = Gtk.Switch()
|
|
216
|
+
grid.attach(label, 0, 0, 1, 1)
|
|
217
|
+
grid.attach(save_locally, 1, 0, 1, 1)
|
|
218
|
+
|
|
219
|
+
label = Gtk.Label(label="Upload both tests ?")
|
|
220
|
+
do_both = Gtk.Switch()
|
|
221
|
+
grid.attach(label, 0, 1, 1, 1)
|
|
222
|
+
grid.attach(do_both, 1, 1, 1, 1)
|
|
223
|
+
|
|
224
|
+
dlg.show_all()
|
|
225
|
+
rc = dlg.run()
|
|
226
|
+
dlg.hide()
|
|
227
|
+
if rc != Gtk.ResponseType.OK:
|
|
228
|
+
dlg.destroy()
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
mdata = self.get_difference_data()
|
|
232
|
+
if save_locally.get_active():
|
|
233
|
+
# Save locally.
|
|
234
|
+
fnam = SensorUtils.build_file_name(mdata)
|
|
547
235
|
fc = Gtk.FileChooserDialog(title="Save data file", action=Gtk.FileChooserAction.SAVE)
|
|
548
236
|
fc.add_buttons(
|
|
549
237
|
Gtk.STOCK_CANCEL,
|
|
@@ -552,17 +240,24 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
552
240
|
Gtk.ResponseType.OK,
|
|
553
241
|
)
|
|
554
242
|
|
|
243
|
+
if self.last_folder:
|
|
244
|
+
fc.set_current_folder(self.last_folder)
|
|
245
|
+
|
|
555
246
|
fc.set_current_name("{}.dat".format(fnam))
|
|
556
247
|
rc = fc.run()
|
|
557
248
|
if rc == Gtk.ResponseType.OK:
|
|
558
|
-
|
|
559
|
-
|
|
249
|
+
SensorUtils.save_sensor_data(fc.get_filename(), mdata)
|
|
250
|
+
|
|
560
251
|
fc.hide()
|
|
561
252
|
fc.destroy()
|
|
562
253
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
254
|
+
# Upload double
|
|
255
|
+
self.upload_test(mdata, self.mod_type["double"])
|
|
256
|
+
|
|
257
|
+
if do_both.get_active():
|
|
258
|
+
self.upload_test(self.mdata["single"], self.mod_type["single"])
|
|
259
|
+
|
|
260
|
+
dlg.destroy()
|
|
566
261
|
|
|
567
262
|
def on_refresh(self, *args):
|
|
568
263
|
"""Refresh canvas."""
|
|
@@ -575,7 +270,7 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
575
270
|
|
|
576
271
|
Args:
|
|
577
272
|
SN (str): Module Serial number.
|
|
578
|
-
|
|
273
|
+
|
|
579
274
|
"""
|
|
580
275
|
md = ITkDButils.get_DB_component(self.session, SN)
|
|
581
276
|
if md is None:
|
|
@@ -585,18 +280,61 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
585
280
|
|
|
586
281
|
return md
|
|
587
282
|
|
|
283
|
+
def update_folder(self, fnam):
|
|
284
|
+
"""Sets last folder."""
|
|
285
|
+
|
|
286
|
+
self.last_folder = Path(fnam).parent.as_posix()
|
|
287
|
+
self.single_file.set_current_folder(self.last_folder)
|
|
288
|
+
self.double_file.set_current_folder(self.last_folder)
|
|
289
|
+
|
|
290
|
+
def show_single_curve(self):
|
|
291
|
+
"""Shows the single curve."""
|
|
292
|
+
try:
|
|
293
|
+
mdata = self.mdata["single"]
|
|
294
|
+
except KeyError:
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
is_module = is_module = "Module_SN" in mdata
|
|
298
|
+
|
|
299
|
+
self.show_curve(
|
|
300
|
+
131,
|
|
301
|
+
mdata["curve"]["V"],
|
|
302
|
+
mdata["curve"]["I"],
|
|
303
|
+
self.mod_type["single"][0:4] if is_module else "Single",
|
|
304
|
+
mdata["curve"]["labels"][0],
|
|
305
|
+
mdata["curve"]["labels"][1],
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def show_double_curve(self):
|
|
309
|
+
"""Shows the double curve."""
|
|
310
|
+
try:
|
|
311
|
+
mdata = self.mdata["double"]
|
|
312
|
+
except KeyError:
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
self.show_curve(
|
|
316
|
+
133,
|
|
317
|
+
mdata["curve"]["V"],
|
|
318
|
+
mdata["curve"]["I"],
|
|
319
|
+
"Double",
|
|
320
|
+
mdata["curve"]["labels"][0],
|
|
321
|
+
None,
|
|
322
|
+
)
|
|
323
|
+
|
|
588
324
|
def on_single_file_set(self, *args):
|
|
589
|
-
"""
|
|
325
|
+
"""Single sensor file chosen."""
|
|
590
326
|
obj_type = ["sensor", "module"]
|
|
591
327
|
fnam = self.single_file.get_filename()
|
|
592
328
|
if fnam is None or not Path(fnam).exists():
|
|
593
329
|
dbGtkUtils.complain("Could not find data file", fnam, parent=self)
|
|
594
330
|
|
|
595
|
-
mdata =
|
|
331
|
+
mdata = SensorUtils.read_sensor_file(fnam)
|
|
332
|
+
self.update_folder(fnam)
|
|
596
333
|
|
|
597
334
|
is_module = 1
|
|
598
335
|
try:
|
|
599
336
|
SN = mdata["Module_SN"]
|
|
337
|
+
|
|
600
338
|
except KeyError:
|
|
601
339
|
SN = mdata["Component"]
|
|
602
340
|
is_module = 0
|
|
@@ -616,39 +354,24 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
616
354
|
|
|
617
355
|
self.single_SN.set_text("{} - {}".format(SN, md["type"]["name"]))
|
|
618
356
|
self.fig.clf()
|
|
619
|
-
self.
|
|
620
|
-
131,
|
|
621
|
-
mdata["curve"]["V"],
|
|
622
|
-
mdata["curve"]["I"],
|
|
623
|
-
self.mod_type["single"][0:4] if is_module else "Single",
|
|
624
|
-
mdata["curve"]["labels"][0],
|
|
625
|
-
mdata["curve"]["labels"][1],
|
|
626
|
-
)
|
|
357
|
+
self.show_single_curve()
|
|
627
358
|
|
|
628
|
-
|
|
629
|
-
"
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if fnam is None or not Path(fnam).exists():
|
|
633
|
-
dbGtkUtils.complain("Could not find data file", fnam, parent=self)
|
|
634
|
-
|
|
635
|
-
mdata = self.read_file(fnam)
|
|
636
|
-
is_module = 1
|
|
637
|
-
# Check SN in data file
|
|
638
|
-
try:
|
|
639
|
-
SN = mdata["Module_SN"]
|
|
640
|
-
except KeyError:
|
|
641
|
-
is_module = 0
|
|
642
|
-
SN = mdata["Component"]
|
|
359
|
+
# Compute difference if single already available
|
|
360
|
+
if "double" in self.mdata:
|
|
361
|
+
self.show_double_curve()
|
|
362
|
+
self.on_difference()
|
|
643
363
|
|
|
644
|
-
|
|
364
|
+
def check_double_SN(self, SN, is_module):
|
|
365
|
+
"""Check that the double SN is a good one."""
|
|
366
|
+
obj_type = ["sensor", "module"]
|
|
645
367
|
if "single" in self.mod_SN:
|
|
646
368
|
if self.mod_SN["single"] == SN:
|
|
647
369
|
dbGtkUtils.complain(
|
|
648
|
-
"Wrong
|
|
370
|
+
"Wrong SN {}".format(SN),
|
|
371
|
+
"{} already used.".format(obj_type[is_module])
|
|
649
372
|
)
|
|
650
373
|
self.double_file.unselect_all()
|
|
651
|
-
return
|
|
374
|
+
return None
|
|
652
375
|
|
|
653
376
|
# Check that it exists in the DB
|
|
654
377
|
if len(SN) != 14 or SN[0:4] != "20US":
|
|
@@ -656,16 +379,21 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
656
379
|
SN = dbGtkUtils.get_a_value(
|
|
657
380
|
"Invalid SN", "Give Ring or corresponding Half Module SN"
|
|
658
381
|
)
|
|
382
|
+
return None
|
|
659
383
|
|
|
660
|
-
self.write_message("Reading data for module {}\n".format(SN))
|
|
661
384
|
md = self.find_module(SN)
|
|
662
385
|
if md is None:
|
|
663
386
|
self.write_message("...object does not exist.\n")
|
|
664
387
|
self.double_file.unselect_all()
|
|
665
|
-
return
|
|
388
|
+
return None
|
|
666
389
|
|
|
390
|
+
return md
|
|
391
|
+
|
|
392
|
+
def get_true_SN(self, md):
|
|
393
|
+
"""Get the actual SN of the 'double' object."""
|
|
667
394
|
found_child = False
|
|
668
|
-
|
|
395
|
+
has_ring = md["type"]["name"].find("Ring")
|
|
396
|
+
if has_ring >= 0:
|
|
669
397
|
self.write_message("...This is a Ring module. Searching children in DB\n")
|
|
670
398
|
for child in md["children"]:
|
|
671
399
|
if child["component"]:
|
|
@@ -695,9 +423,39 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
695
423
|
str(ITkDButils.get_db_response()),
|
|
696
424
|
)
|
|
697
425
|
self.double_file.unselect_all()
|
|
698
|
-
return
|
|
426
|
+
return None
|
|
699
427
|
|
|
700
428
|
self.write_message("... {}\n".format(halfM_SN))
|
|
429
|
+
return halfM_SN
|
|
430
|
+
|
|
431
|
+
return md["serialNumber"]
|
|
432
|
+
|
|
433
|
+
def on_double_file_set(self, *args):
|
|
434
|
+
"File chosen for the 'double module'"
|
|
435
|
+
obj_type = ["sensor", "module"]
|
|
436
|
+
fnam = self.double_file.get_filename()
|
|
437
|
+
if fnam is None or not Path(fnam).exists():
|
|
438
|
+
dbGtkUtils.complain("Could not find data file", fnam, parent=self)
|
|
439
|
+
|
|
440
|
+
mdata = SensorUtils.read_sensor_file(fnam)
|
|
441
|
+
self.update_folder(fnam)
|
|
442
|
+
is_module = 1
|
|
443
|
+
# Check SN in data file
|
|
444
|
+
try:
|
|
445
|
+
SN = mdata["Module_SN"]
|
|
446
|
+
except KeyError:
|
|
447
|
+
is_module = 0
|
|
448
|
+
SN = mdata["Component"]
|
|
449
|
+
|
|
450
|
+
halfM_SN = SN
|
|
451
|
+
md = self.check_double_SN(SN, is_module)
|
|
452
|
+
if md is None:
|
|
453
|
+
return
|
|
454
|
+
|
|
455
|
+
self.write_message("Reading data for {} {}\n".format(obj_type[is_module], SN))
|
|
456
|
+
halfM_SN = self.get_true_SN(md)
|
|
457
|
+
if halfM_SN is None:
|
|
458
|
+
return
|
|
701
459
|
|
|
702
460
|
if "single" in self.mod_type:
|
|
703
461
|
if is_module and self.mod_type["single"] == md["type"]["code"]:
|
|
@@ -714,14 +472,7 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
714
472
|
self.mdata["double"] = mdata
|
|
715
473
|
|
|
716
474
|
self.double_SN.set_text("{} - {}".format(halfM_SN, md["type"]["name"]))
|
|
717
|
-
self.
|
|
718
|
-
133,
|
|
719
|
-
mdata["curve"]["V"],
|
|
720
|
-
mdata["curve"]["I"],
|
|
721
|
-
"Double",
|
|
722
|
-
mdata["curve"]["labels"][0],
|
|
723
|
-
None,
|
|
724
|
-
)
|
|
475
|
+
self.show_double_curve()
|
|
725
476
|
|
|
726
477
|
# Compute difference if single already available
|
|
727
478
|
if "single" in self.mdata:
|
|
@@ -737,7 +488,7 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
737
488
|
|
|
738
489
|
is_module = "Module_SN" in self.mdata["double"]
|
|
739
490
|
double_I = self.mdata["double"]["curve"]["I"]
|
|
740
|
-
single_I = scale_iv(
|
|
491
|
+
single_I = SensorUtils.scale_iv(
|
|
741
492
|
self.mdata["single"]["curve"]["I"],
|
|
742
493
|
self.mdata["single"]["Temperature"] + 273.0,
|
|
743
494
|
self.mdata["double"]["Temperature"] + 273.0,
|
|
@@ -781,51 +532,9 @@ class IVwindow(dbGtkUtils.ITkDBWindow):
|
|
|
781
532
|
ax.grid()
|
|
782
533
|
self.on_refresh()
|
|
783
534
|
|
|
784
|
-
@staticmethod
|
|
785
|
-
def read_file(fnam):
|
|
786
|
-
"""Read a data file. Return dictionary with all teh data."""
|
|
787
|
-
labels = []
|
|
788
|
-
metadata = {}
|
|
789
|
-
with open(fnam, "r", encoding="utf-8") as ifile:
|
|
790
|
-
first = True
|
|
791
|
-
for line in ifile:
|
|
792
|
-
if first:
|
|
793
|
-
first = False
|
|
794
|
-
ipos = line.rfind(".")
|
|
795
|
-
metadata["fname"] = line[:ipos]
|
|
796
|
-
continue
|
|
797
|
-
|
|
798
|
-
if line.find("Voltage [V]") >= 0 or line.find("Voltage[V]") >= 0:
|
|
799
|
-
labels = line.split("\t")
|
|
800
|
-
break
|
|
801
|
-
|
|
802
|
-
rc = line.find(":")
|
|
803
|
-
if rc >= 0:
|
|
804
|
-
key = line[:rc].strip()
|
|
805
|
-
val = line[rc + 1 :].strip()
|
|
806
|
-
if key in ["Temperature", "Humidity"]:
|
|
807
|
-
metadata[key] = float(val)
|
|
808
|
-
else:
|
|
809
|
-
metadata[key] = val
|
|
810
|
-
|
|
811
|
-
V = []
|
|
812
|
-
I = []
|
|
813
|
-
for line in ifile:
|
|
814
|
-
data = [float(s) for s in line.split()]
|
|
815
|
-
V.append(data[0])
|
|
816
|
-
I.append(data[1])
|
|
817
|
-
|
|
818
|
-
metadata["curve"] = {
|
|
819
|
-
"V": np.abs(np.array(V)),
|
|
820
|
-
"I": np.abs(np.array(I)),
|
|
821
|
-
"labels": labels[0:2],
|
|
822
|
-
}
|
|
823
|
-
return metadata
|
|
824
|
-
|
|
825
535
|
|
|
826
536
|
def main():
|
|
827
537
|
"""Main entryy."""
|
|
828
|
-
import sys
|
|
829
538
|
|
|
830
539
|
# DB login
|
|
831
540
|
dlg = ITkDBlogin.ITkDBlogin()
|