##// END OF EJS Templates
new script "schain".
Miguel Valdez -
r830:6daeab89ec33
parent child
Show More
@@ -0,0 +1,49
1 #!/usr/bin/env python
2 '''
3 Created on Jul 7, 2014
4
5 @author: roj-idl71
6 '''
7 import os, sys
8
9 from schainpy import controller_api
10 from optparse import OptionParser
11
12 USAGE = """This script executes Signal Chain using parameters stored in [filename].
13
14 $ schain --file=[filename]
15 """
16
17 def main(filename):
18
19 controllerObj = controller_api.ControllerThread()
20 if not controllerObj.readXml(filename):
21 return
22
23 #Configure use of external plotter before start
24 plotterObj = controllerObj.useExternalPlotter()
25 ########################################
26
27 controllerObj.start()
28 plotterObj.start()
29
30 print "Finishing all processes ..."
31
32 controllerObj.join(5)
33
34 print "End of script"
35
36 if __name__ == '__main__':
37
38 parser = OptionParser(usage=USAGE)
39
40 parser.add_option("-f", "--file", type="string", default="",
41 help="File containing schain parameters")
42
43 (op, args) = parser.parse_args()
44
45 if not op.file:
46 parser.print_help()
47 sys.exit(0)
48
49 main(op.file) No newline at end of file
@@ -1,84 +1,85
1 1 VERSIONS:
2 2
3 3 2.1.2:
4 4 -jroutils_ftp.py: Bug fixed, Any error sending file stopped the Server Thread
5 5 Server thread opens and closes remote server each time file list is sent
6 6 -jroplot_spectra.py: Noise path was not being created when noise data is saved.
7 7 -jroIO_base.py: startTime can be greater than endTime. Example: SpreadF [18:00 - 07:00]
8 8
9 9 2.1.3:
10 10 -jroplot_heispectra.py: SpectraHeisScope was not showing the right channels
11 11 -jroproc_voltage.py: Bug fixed selecting profiles (self.nProfiles took a wrong value),
12 12 Bug fixed selecting heights by block (selecting profiles instead heights)
13 13 -jroproc_voltage.py: New feature added: decoding data by block using FFT.
14 14 -jroIO_heispectra.py: Bug fixed in FitsReader. Using local Fits instance instead schainpy.mode.data.jrodata.Fits.
15 15 -jroIO_heispectra.py: Channel index list does not exist.
16 16
17 17 2.1.3.1:
18 18 -GUI: every icon were resized
19 19 -jroproc_voltage.py: Print a message when "Read from code" option is selected and the code is not defined inside data file
20 20
21 21 2.1.3.2:
22 22 -GUI: user interaction enhanced
23 23 -controller_api.py: Safe access to ControllerThead
24 24
25 25 2.1.3.3:
26 26 -Colored Button Icons were added to GUI
27 27
28 28 2.1.4:
29 29 -Sending error notifications to signal chain administrator
30 30 -Login to email server added
31 31
32 32 2.1.4.1:
33 33 -Send notifications when an error different to ValueError is detected
34 34
35 35 2.1.4.2:
36 36 -A new Plotter Class was added
37 37 -Project.start() does not accept filename as a parameter anymore
38 38
39 39 2.1.5:
40 40 -serializer module added to Signal Chain
41 41 -jroplotter.py added to Signal Chain
42 42
43 43 2.2.0:
44 44 -GUI: use of external plotter
45 45 -Compatible with matplotlib 1.5.0
46 46
47 47 2.2.1:
48 48 -Bugs fixed in GUI
49 49 -Views were improved in GUI
50 50 -Support to MST-ISR experiments
51 51 -Bug fixed getting noise using hyldebrant. (minimum number of points > 20%)
52 52 -handleError added to jroplotter.py
53 53
54 54 2.2.2:
55 55 -VoltageProc: ProfileSelector, Reshape, Decoder with nTxs!=1 and getblock=True was tested
56 56 -Rawdata and testRawdata.py added to Signal Chain project
57 57
58 58 2.2.3:
59 59 -Bug fixed in GUI: Error getting(reading) Code value
60 60 -Bug fixed in GUI: Flip option always needs channelList field
61 61 -Bug fixed in jrodata: when one branch modified a value in "dataOut" (example: dataOut.code) this value
62 62 was modified for every branch (because this was a reference). It was modified in data.copy()
63 63 -Bug fixed in jroproc_voltage.profileSelector(): rangeList replaces to profileRangeList.
64 64
65 65
66 66 2.2.3.1:
67 67 -Filtering block by time has been added.
68 68 -Bug fixed plotting RTI, CoherenceMap and others using xmin and xmax parameters. The first day worked
69 69 properly but the next days did not.
70 70
71 71 2.2.4:
72 72 -jroproc_spectra_lags.py added to schainpy
73 73 -Bug fixed in schainGUI: ProcUnit was created with the same id in some cases.
74 74 -Bug fixed in jroHeaderIO: Header size validation.
75 75
76 76 2.2.4.1:
77 77 -jroIO_usrp.py is update to read Sandra's data
78 78 -decimation in Spectra and RTI plots is always enabled.
79 79 -time-window option added to GUI
80 80
81 81 2.2.5:
82 82 -splitProfiles and combineProfiles modules were added to VoltageProc and Signal Chain GUI.
83 83 -nProfiles of USRP data (hdf5) is the number of profiles thera are in one second.
84 -jroPlotter works directly with data objects instead of dictionaries No newline at end of file
84 -jroPlotter works directly with data objects instead of dictionaries
85 -script "schain" was added to Signal Chain installer No newline at end of file
@@ -1,1290 +1,1294
1 1 '''
2 2 Created on September , 2012
3 3 @author:
4 4 '''
5 5
6 6 import sys
7 7 import ast
8 8 import datetime
9 9 import traceback
10 10 import schainpy
11 11 import schainpy.admin
12 12
13 13 from xml.etree.ElementTree import ElementTree, Element, SubElement, tostring
14 14 from xml.dom import minidom
15 15
16 16 from schainpy.model import *
17 17 from time import sleep
18 18
19 19 def prettify(elem):
20 20 """Return a pretty-printed XML string for the Element.
21 21 """
22 22 rough_string = tostring(elem, 'utf-8')
23 23 reparsed = minidom.parseString(rough_string)
24 24 return reparsed.toprettyxml(indent=" ")
25 25
26 26 class ParameterConf():
27 27
28 28 id = None
29 29 name = None
30 30 value = None
31 31 format = None
32 32
33 33 __formated_value = None
34 34
35 35 ELEMENTNAME = 'Parameter'
36 36
37 37 def __init__(self):
38 38
39 39 self.format = 'str'
40 40
41 41 def getElementName(self):
42 42
43 43 return self.ELEMENTNAME
44 44
45 45 def getValue(self):
46 46
47 47 value = self.value
48 48 format = self.format
49 49
50 50 if self.__formated_value != None:
51 51
52 52 return self.__formated_value
53 53
54 54 if format == 'str':
55 55 self.__formated_value = str(value)
56 56 return self.__formated_value
57 57
58 58 if value == '':
59 59 raise ValueError, "%s: This parameter value is empty" %self.name
60 60
61 61 if format == 'list':
62 62 strList = value.split(',')
63 63
64 64 self.__formated_value = strList
65 65
66 66 return self.__formated_value
67 67
68 68 if format == 'intlist':
69 69 """
70 70 Example:
71 71 value = (0,1,2)
72 72 """
73 73
74 74 new_value = ast.literal_eval(value)
75 75
76 76 if type(new_value) not in (tuple, list):
77 77 new_value = [int(new_value)]
78 78
79 79 self.__formated_value = new_value
80 80
81 81 return self.__formated_value
82 82
83 83 if format == 'floatlist':
84 84 """
85 85 Example:
86 86 value = (0.5, 1.4, 2.7)
87 87 """
88 88
89 89 new_value = ast.literal_eval(value)
90 90
91 91 if type(new_value) not in (tuple, list):
92 92 new_value = [float(new_value)]
93 93
94 94 self.__formated_value = new_value
95 95
96 96 return self.__formated_value
97 97
98 98 if format == 'date':
99 99 strList = value.split('/')
100 100 intList = [int(x) for x in strList]
101 101 date = datetime.date(intList[0], intList[1], intList[2])
102 102
103 103 self.__formated_value = date
104 104
105 105 return self.__formated_value
106 106
107 107 if format == 'time':
108 108 strList = value.split(':')
109 109 intList = [int(x) for x in strList]
110 110 time = datetime.time(intList[0], intList[1], intList[2])
111 111
112 112 self.__formated_value = time
113 113
114 114 return self.__formated_value
115 115
116 116 if format == 'pairslist':
117 117 """
118 118 Example:
119 119 value = (0,1),(1,2)
120 120 """
121 121
122 122 new_value = ast.literal_eval(value)
123 123
124 124 if type(new_value) not in (tuple, list):
125 125 raise ValueError, "%s has to be a tuple or list of pairs" %value
126 126
127 127 if type(new_value[0]) not in (tuple, list):
128 128 if len(new_value) != 2:
129 129 raise ValueError, "%s has to be a tuple or list of pairs" %value
130 130 new_value = [new_value]
131 131
132 132 for thisPair in new_value:
133 133 if len(thisPair) != 2:
134 134 raise ValueError, "%s has to be a tuple or list of pairs" %value
135 135
136 136 self.__formated_value = new_value
137 137
138 138 return self.__formated_value
139 139
140 140 if format == 'multilist':
141 141 """
142 142 Example:
143 143 value = (0,1,2),(3,4,5)
144 144 """
145 145 multiList = ast.literal_eval(value)
146 146
147 147 if type(multiList[0]) == int:
148 148 multiList = ast.literal_eval("(" + value + ")")
149 149
150 150 self.__formated_value = multiList
151 151
152 152 return self.__formated_value
153 153
154 154 if format == 'bool':
155 155 value = int(value)
156 156
157 157 if format == 'int':
158 158 value = float(value)
159 159
160 160 format_func = eval(format)
161 161
162 162 self.__formated_value = format_func(value)
163 163
164 164 return self.__formated_value
165 165
166 166 def updateId(self, new_id):
167 167
168 168 self.id = str(new_id)
169 169
170 170 def setup(self, id, name, value, format='str'):
171 171
172 172 self.id = str(id)
173 173 self.name = name
174 174 self.value = str(value)
175 175 self.format = str.lower(format)
176 176
177 177 self.getValue()
178 178
179 179 return 1
180 180
181 181 def update(self, name, value, format='str'):
182 182
183 183 self.name = name
184 184 self.value = str(value)
185 185 self.format = format
186 186
187 187 def makeXml(self, opElement):
188 188
189 189 parmElement = SubElement(opElement, self.ELEMENTNAME)
190 190 parmElement.set('id', str(self.id))
191 191 parmElement.set('name', self.name)
192 192 parmElement.set('value', self.value)
193 193 parmElement.set('format', self.format)
194 194
195 195 def readXml(self, parmElement):
196 196
197 197 self.id = parmElement.get('id')
198 198 self.name = parmElement.get('name')
199 199 self.value = parmElement.get('value')
200 200 self.format = str.lower(parmElement.get('format'))
201 201
202 202 #Compatible with old signal chain version
203 203 if self.format == 'int' and self.name == 'idfigure':
204 204 self.name = 'id'
205 205
206 206 def printattr(self):
207 207
208 208 print "Parameter[%s]: name = %s, value = %s, format = %s" %(self.id, self.name, self.value, self.format)
209 209
210 210 class OperationConf():
211 211
212 212 id = None
213 213 name = None
214 214 priority = None
215 215 type = None
216 216
217 217 parmConfObjList = []
218 218
219 219 ELEMENTNAME = 'Operation'
220 220
221 221 def __init__(self):
222 222
223 223 self.id = '0'
224 224 self.name = None
225 225 self.priority = None
226 226 self.type = 'self'
227 227
228 228
229 229 def __getNewId(self):
230 230
231 231 return int(self.id)*10 + len(self.parmConfObjList) + 1
232 232
233 233 def updateId(self, new_id):
234 234
235 235 self.id = str(new_id)
236 236
237 237 n = 1
238 238 for parmObj in self.parmConfObjList:
239 239
240 240 idParm = str(int(new_id)*10 + n)
241 241 parmObj.updateId(idParm)
242 242
243 243 n += 1
244 244
245 245 def getElementName(self):
246 246
247 247 return self.ELEMENTNAME
248 248
249 249 def getParameterObjList(self):
250 250
251 251 return self.parmConfObjList
252 252
253 253 def getParameterObj(self, parameterName):
254 254
255 255 for parmConfObj in self.parmConfObjList:
256 256
257 257 if parmConfObj.name != parameterName:
258 258 continue
259 259
260 260 return parmConfObj
261 261
262 262 return None
263 263
264 264 def getParameterObjfromValue(self, parameterValue):
265 265
266 266 for parmConfObj in self.parmConfObjList:
267 267
268 268 if parmConfObj.getValue() != parameterValue:
269 269 continue
270 270
271 271 return parmConfObj.getValue()
272 272
273 273 return None
274 274
275 275 def getParameterValue(self, parameterName):
276 276
277 277 parameterObj = self.getParameterObj(parameterName)
278 278
279 279 # if not parameterObj:
280 280 # return None
281 281
282 282 value = parameterObj.getValue()
283 283
284 284 return value
285 285
286 286 def setup(self, id, name, priority, type):
287 287
288 288 self.id = str(id)
289 289 self.name = name
290 290 self.type = type
291 291 self.priority = priority
292 292
293 293 self.parmConfObjList = []
294 294
295 295 def removeParameters(self):
296 296
297 297 for obj in self.parmConfObjList:
298 298 del obj
299 299
300 300 self.parmConfObjList = []
301 301
302 302 def addParameter(self, name, value, format='str'):
303 303
304 304 id = self.__getNewId()
305 305
306 306 parmConfObj = ParameterConf()
307 307 if not parmConfObj.setup(id, name, value, format):
308 308 return None
309 309
310 310 self.parmConfObjList.append(parmConfObj)
311 311
312 312 return parmConfObj
313 313
314 314 def changeParameter(self, name, value, format='str'):
315 315
316 316 parmConfObj = self.getParameterObj(name)
317 317 parmConfObj.update(name, value, format)
318 318
319 319 return parmConfObj
320 320
321 321 def makeXml(self, procUnitElement):
322 322
323 323 opElement = SubElement(procUnitElement, self.ELEMENTNAME)
324 324 opElement.set('id', str(self.id))
325 325 opElement.set('name', self.name)
326 326 opElement.set('type', self.type)
327 327 opElement.set('priority', str(self.priority))
328 328
329 329 for parmConfObj in self.parmConfObjList:
330 330 parmConfObj.makeXml(opElement)
331 331
332 332 def readXml(self, opElement):
333 333
334 334 self.id = opElement.get('id')
335 335 self.name = opElement.get('name')
336 336 self.type = opElement.get('type')
337 337 self.priority = opElement.get('priority')
338 338
339 339 #Compatible with old signal chain version
340 340 #Use of 'run' method instead 'init'
341 341 if self.type == 'self' and self.name == 'init':
342 342 self.name = 'run'
343 343
344 344 self.parmConfObjList = []
345 345
346 346 parmElementList = opElement.getiterator(ParameterConf().getElementName())
347 347
348 348 for parmElement in parmElementList:
349 349 parmConfObj = ParameterConf()
350 350 parmConfObj.readXml(parmElement)
351 351
352 352 #Compatible with old signal chain version
353 353 #If an 'plot' OPERATION is found, changes name operation by the value of its type PARAMETER
354 354 if self.type != 'self' and self.name == 'Plot':
355 355 if parmConfObj.format == 'str' and parmConfObj.name == 'type':
356 356 self.name = parmConfObj.value
357 357 continue
358 358
359 359 self.parmConfObjList.append(parmConfObj)
360 360
361 361 def printattr(self):
362 362
363 363 print "%s[%s]: name = %s, type = %s, priority = %s" %(self.ELEMENTNAME,
364 364 self.id,
365 365 self.name,
366 366 self.type,
367 367 self.priority)
368 368
369 369 for parmConfObj in self.parmConfObjList:
370 370 parmConfObj.printattr()
371 371
372 372 def createObject(self, plotter_queue=None):
373 373
374 374 if self.type == 'self':
375 375 raise ValueError, "This operation type cannot be created"
376 376
377 377 if self.type == 'plotter':
378 378 #Plotter(plotter_name)
379 379 if not plotter_queue:
380 380 raise ValueError, "plotter_queue is not defined. Use:\nmyProject = Project()\nmyProject.setPlotterQueue(plotter_queue)"
381 381
382 382 opObj = Plotter(self.name, plotter_queue)
383 383
384 384 if self.type == 'external' or self.type == 'other':
385 385 className = eval(self.name)
386 386 opObj = className()
387 387
388 388 return opObj
389 389
390 390 class ProcUnitConf():
391 391
392 392 id = None
393 393 name = None
394 394 datatype = None
395 395 inputId = None
396 396 parentId = None
397 397
398 398 opConfObjList = []
399 399
400 400 procUnitObj = None
401 401 opObjList = []
402 402
403 403 ELEMENTNAME = 'ProcUnit'
404 404
405 405 def __init__(self):
406 406
407 407 self.id = None
408 408 self.datatype = None
409 409 self.name = None
410 410 self.inputId = None
411 411
412 412 self.opConfObjList = []
413 413
414 414 self.procUnitObj = None
415 415 self.opObjDict = {}
416 416
417 417 def __getPriority(self):
418 418
419 419 return len(self.opConfObjList)+1
420 420
421 421 def __getNewId(self):
422 422
423 423 return int(self.id)*10 + len(self.opConfObjList) + 1
424 424
425 425 def getElementName(self):
426 426
427 427 return self.ELEMENTNAME
428 428
429 429 def getId(self):
430 430
431 431 return self.id
432 432
433 433 def updateId(self, new_id, parentId=parentId):
434 434
435 435
436 436 new_id = int(parentId)*10 + (int(self.id) % 10)
437 437 new_inputId = int(parentId)*10 + (int(self.inputId) % 10)
438 438
439 439 #If this proc unit has not inputs
440 440 if self.inputId == '0':
441 441 new_inputId = 0
442 442
443 443 n = 1
444 444 for opConfObj in self.opConfObjList:
445 445
446 446 idOp = str(int(new_id)*10 + n)
447 447 opConfObj.updateId(idOp)
448 448
449 449 n += 1
450 450
451 451 self.parentId = str(parentId)
452 452 self.id = str(new_id)
453 453 self.inputId = str(new_inputId)
454 454
455 455
456 456 def getInputId(self):
457 457
458 458 return self.inputId
459 459
460 460 def getOperationObjList(self):
461 461
462 462 return self.opConfObjList
463 463
464 464 def getOperationObj(self, name=None):
465 465
466 466 for opConfObj in self.opConfObjList:
467 467
468 468 if opConfObj.name != name:
469 469 continue
470 470
471 471 return opConfObj
472 472
473 473 return None
474 474
475 475 def getOpObjfromParamValue(self, value=None):
476 476
477 477 for opConfObj in self.opConfObjList:
478 478 if opConfObj.getParameterObjfromValue(parameterValue=value) != value:
479 479 continue
480 480 return opConfObj
481 481 return None
482 482
483 483 def getProcUnitObj(self):
484 484
485 485 return self.procUnitObj
486 486
487 487 def setup(self, id, name, datatype, inputId, parentId=None):
488 488
489 489 #Compatible with old signal chain version
490 490 if datatype==None and name==None:
491 491 raise ValueError, "datatype or name should be defined"
492 492
493 493 if name==None:
494 494 if 'Proc' in datatype:
495 495 name = datatype
496 496 else:
497 497 name = '%sProc' %(datatype)
498 498
499 499 if datatype==None:
500 500 datatype = name.replace('Proc','')
501 501
502 502 self.id = str(id)
503 503 self.name = name
504 504 self.datatype = datatype
505 505 self.inputId = inputId
506 506 self.parentId = parentId
507 507
508 508 self.opConfObjList = []
509 509
510 510 self.addOperation(name='run', optype='self')
511 511
512 512 def removeOperations(self):
513 513
514 514 for obj in self.opConfObjList:
515 515 del obj
516 516
517 517 self.opConfObjList = []
518 518 self.addOperation(name='run')
519 519
520 520 def addParameter(self, **kwargs):
521 521 '''
522 522 Add parameters to "run" operation
523 523 '''
524 524 opObj = self.opConfObjList[0]
525 525
526 526 opObj.addParameter(**kwargs)
527 527
528 528 return opObj
529 529
530 530 def addOperation(self, name, optype='self'):
531 531
532 532 id = self.__getNewId()
533 533 priority = self.__getPriority()
534 534
535 535 opConfObj = OperationConf()
536 536 opConfObj.setup(id, name=name, priority=priority, type=optype)
537 537
538 538 self.opConfObjList.append(opConfObj)
539 539
540 540 return opConfObj
541 541
542 542 def makeXml(self, projectElement):
543 543
544 544 procUnitElement = SubElement(projectElement, self.ELEMENTNAME)
545 545 procUnitElement.set('id', str(self.id))
546 546 procUnitElement.set('name', self.name)
547 547 procUnitElement.set('datatype', self.datatype)
548 548 procUnitElement.set('inputId', str(self.inputId))
549 549
550 550 for opConfObj in self.opConfObjList:
551 551 opConfObj.makeXml(procUnitElement)
552 552
553 553 def readXml(self, upElement):
554 554
555 555 self.id = upElement.get('id')
556 556 self.name = upElement.get('name')
557 557 self.datatype = upElement.get('datatype')
558 558 self.inputId = upElement.get('inputId')
559 559
560 560 if self.ELEMENTNAME == "ReadUnit":
561 561 self.datatype = self.datatype.replace("Reader", "")
562 562
563 563 if self.ELEMENTNAME == "ProcUnit":
564 564 self.datatype = self.datatype.replace("Proc", "")
565 565
566 566 if self.inputId == 'None':
567 567 self.inputId = '0'
568 568
569 569 self.opConfObjList = []
570 570
571 571 opElementList = upElement.getiterator(OperationConf().getElementName())
572 572
573 573 for opElement in opElementList:
574 574 opConfObj = OperationConf()
575 575 opConfObj.readXml(opElement)
576 576 self.opConfObjList.append(opConfObj)
577 577
578 578 def printattr(self):
579 579
580 580 print "%s[%s]: name = %s, datatype = %s, inputId = %s" %(self.ELEMENTNAME,
581 581 self.id,
582 582 self.name,
583 583 self.datatype,
584 584 self.inputId)
585 585
586 586 for opConfObj in self.opConfObjList:
587 587 opConfObj.printattr()
588 588
589 589 def createObjects(self, plotter_queue=None):
590 590
591 591 className = eval(self.name)
592 592 procUnitObj = className()
593 593
594 594 for opConfObj in self.opConfObjList:
595 595
596 596 if opConfObj.type == 'self':
597 597 continue
598 598
599 599 opObj = opConfObj.createObject(plotter_queue)
600 600
601 601 self.opObjDict[opConfObj.id] = opObj
602 602 procUnitObj.addOperation(opObj, opConfObj.id)
603 603
604 604 self.procUnitObj = procUnitObj
605 605
606 606 return procUnitObj
607 607
608 608 def run(self):
609 609
610 610 is_ok = False
611 611
612 612 for opConfObj in self.opConfObjList:
613 613
614 614 kwargs = {}
615 615 for parmConfObj in opConfObj.getParameterObjList():
616 616 if opConfObj.name == 'run' and parmConfObj.name == 'datatype':
617 617 continue
618 618
619 619 kwargs[parmConfObj.name] = parmConfObj.getValue()
620 620
621 621 ini = time.time()
622 622
623 623 #print "\tRunning the '%s' operation with %s" %(opConfObj.name, opConfObj.id)
624 624 sts = self.procUnitObj.call(opType = opConfObj.type,
625 625 opName = opConfObj.name,
626 626 opId = opConfObj.id,
627 627 **kwargs)
628 628
629 629 # total_time = time.time() - ini
630 630 #
631 631 # if total_time > 0.002:
632 632 # print "%s::%s took %f seconds" %(self.name, opConfObj.name, total_time)
633 633
634 634 is_ok = is_ok or sts
635 635
636 636 return is_ok
637 637
638 638 def close(self):
639 639
640 640 for opConfObj in self.opConfObjList:
641 641 if opConfObj.type == 'self':
642 642 continue
643 643
644 644 opObj = self.procUnitObj.getOperationObj(opConfObj.id)
645 645 opObj.close()
646 646
647 647 self.procUnitObj.close()
648 648
649 649 return
650 650
651 651 class ReadUnitConf(ProcUnitConf):
652 652
653 653 path = None
654 654 startDate = None
655 655 endDate = None
656 656 startTime = None
657 657 endTime = None
658 658
659 659 ELEMENTNAME = 'ReadUnit'
660 660
661 661 def __init__(self):
662 662
663 663 self.id = None
664 664 self.datatype = None
665 665 self.name = None
666 666 self.inputId = None
667 667
668 668 self.parentId = None
669 669
670 670 self.opConfObjList = []
671 671 self.opObjList = []
672 672
673 673 def getElementName(self):
674 674
675 675 return self.ELEMENTNAME
676 676
677 677 def setup(self, id, name, datatype, path, startDate="", endDate="", startTime="", endTime="", parentId=None, **kwargs):
678 678
679 679 #Compatible with old signal chain version
680 680 if datatype==None and name==None:
681 681 raise ValueError, "datatype or name should be defined"
682 682
683 683 if name==None:
684 684 if 'Reader' in datatype:
685 685 name = datatype
686 686 else:
687 687 name = '%sReader' %(datatype)
688 688
689 689 if datatype==None:
690 690 datatype = name.replace('Reader','')
691 691
692 692 self.id = id
693 693 self.name = name
694 694 self.datatype = datatype
695 695
696 696 self.path = os.path.abspath(path)
697 697 self.startDate = startDate
698 698 self.endDate = endDate
699 699 self.startTime = startTime
700 700 self.endTime = endTime
701 701
702 702 self.inputId = '0'
703 703 self.parentId = parentId
704 704
705 705 self.addRunOperation(**kwargs)
706 706
707 707 def update(self, datatype, path, startDate, endDate, startTime, endTime, parentId=None, name=None, **kwargs):
708 708
709 709 #Compatible with old signal chain version
710 710 if datatype==None and name==None:
711 711 raise ValueError, "datatype or name should be defined"
712 712
713 713 if name==None:
714 714 if 'Reader' in datatype:
715 715 name = datatype
716 716 else:
717 717 name = '%sReader' %(datatype)
718 718
719 719 if datatype==None:
720 720 datatype = name.replace('Reader','')
721 721
722 722 self.datatype = datatype
723 723 self.name = name
724 724 self.path = path
725 725 self.startDate = startDate
726 726 self.endDate = endDate
727 727 self.startTime = startTime
728 728 self.endTime = endTime
729 729
730 730 self.inputId = '0'
731 731 self.parentId = parentId
732 732
733 733 self.updateRunOperation(**kwargs)
734 734
735 735 def removeOperations(self):
736 736
737 737 for obj in self.opConfObjList:
738 738 del obj
739 739
740 740 self.opConfObjList = []
741 741
742 742 def addRunOperation(self, **kwargs):
743 743
744 744 opObj = self.addOperation(name = 'run', optype = 'self')
745 745
746 746 opObj.addParameter(name='datatype' , value=self.datatype, format='str')
747 747 opObj.addParameter(name='path' , value=self.path, format='str')
748 748 opObj.addParameter(name='startDate' , value=self.startDate, format='date')
749 749 opObj.addParameter(name='endDate' , value=self.endDate, format='date')
750 750 opObj.addParameter(name='startTime' , value=self.startTime, format='time')
751 751 opObj.addParameter(name='endTime' , value=self.endTime, format='time')
752 752
753 753 for key, value in kwargs.items():
754 754 opObj.addParameter(name=key, value=value, format=type(value).__name__)
755 755
756 756 return opObj
757 757
758 758 def updateRunOperation(self, **kwargs):
759 759
760 760 opObj = self.getOperationObj(name = 'run')
761 761 opObj.removeParameters()
762 762
763 763 opObj.addParameter(name='datatype' , value=self.datatype, format='str')
764 764 opObj.addParameter(name='path' , value=self.path, format='str')
765 765 opObj.addParameter(name='startDate' , value=self.startDate, format='date')
766 766 opObj.addParameter(name='endDate' , value=self.endDate, format='date')
767 767 opObj.addParameter(name='startTime' , value=self.startTime, format='time')
768 768 opObj.addParameter(name='endTime' , value=self.endTime, format='time')
769 769
770 770 for key, value in kwargs.items():
771 771 opObj.addParameter(name=key, value=value, format=type(value).__name__)
772 772
773 773 return opObj
774 774
775 775 # def makeXml(self, projectElement):
776 776 #
777 777 # procUnitElement = SubElement(projectElement, self.ELEMENTNAME)
778 778 # procUnitElement.set('id', str(self.id))
779 779 # procUnitElement.set('name', self.name)
780 780 # procUnitElement.set('datatype', self.datatype)
781 781 # procUnitElement.set('inputId', str(self.inputId))
782 782 #
783 783 # for opConfObj in self.opConfObjList:
784 784 # opConfObj.makeXml(procUnitElement)
785 785
786 786 def readXml(self, upElement):
787 787
788 788 self.id = upElement.get('id')
789 789 self.name = upElement.get('name')
790 790 self.datatype = upElement.get('datatype')
791 791 self.inputId = upElement.get('inputId')
792 792
793 793 if self.ELEMENTNAME == "ReadUnit":
794 794 self.datatype = self.datatype.replace("Reader", "")
795 795
796 796 if self.inputId == 'None':
797 797 self.inputId = '0'
798 798
799 799 self.opConfObjList = []
800 800
801 801 opElementList = upElement.getiterator(OperationConf().getElementName())
802 802
803 803 for opElement in opElementList:
804 804 opConfObj = OperationConf()
805 805 opConfObj.readXml(opElement)
806 806 self.opConfObjList.append(opConfObj)
807 807
808 808 if opConfObj.name == 'run':
809 809 self.path = opConfObj.getParameterValue('path')
810 810 self.startDate = opConfObj.getParameterValue('startDate')
811 811 self.endDate = opConfObj.getParameterValue('endDate')
812 812 self.startTime = opConfObj.getParameterValue('startTime')
813 813 self.endTime = opConfObj.getParameterValue('endTime')
814 814
815 815 class Project():
816 816
817 817 id = None
818 818 name = None
819 819 description = None
820 820 filename = None
821 821
822 822 procUnitConfObjDict = None
823 823
824 824 ELEMENTNAME = 'Project'
825 825
826 826 plotterQueue = None
827 827
828 828 def __init__(self, plotter_queue=None):
829 829
830 830 self.id = None
831 831 self.name = None
832 832 self.description = None
833 833
834 834 self.plotterQueue = plotter_queue
835 835
836 836 self.procUnitConfObjDict = {}
837 837
838 838 def __getNewId(self):
839 839
840 840 idList = self.procUnitConfObjDict.keys()
841 841
842 842 id = int(self.id)*10
843 843
844 844 while True:
845 845 id += 1
846 846
847 847 if str(id) in idList:
848 848 continue
849 849
850 850 break
851 851
852 852 return str(id)
853 853
854 854 def getElementName(self):
855 855
856 856 return self.ELEMENTNAME
857 857
858 858 def getId(self):
859 859
860 860 return self.id
861 861
862 862 def updateId(self, new_id):
863 863
864 864 self.id = str(new_id)
865 865
866 866 keyList = self.procUnitConfObjDict.keys()
867 867 keyList.sort()
868 868
869 869 n = 1
870 870 newProcUnitConfObjDict = {}
871 871
872 872 for procKey in keyList:
873 873
874 874 procUnitConfObj = self.procUnitConfObjDict[procKey]
875 875 idProcUnit = str(int(self.id)*10 + n)
876 876 procUnitConfObj.updateId(idProcUnit, parentId = self.id)
877 877
878 878 newProcUnitConfObjDict[idProcUnit] = procUnitConfObj
879 879 n += 1
880 880
881 881 self.procUnitConfObjDict = newProcUnitConfObjDict
882 882
883 883 def setup(self, id, name, description):
884 884
885 885 self.id = str(id)
886 886 self.name = name
887 887 self.description = description
888 888
889 889 def update(self, name, description):
890 890
891 891 self.name = name
892 892 self.description = description
893 893
894 894 def addReadUnit(self, id=None, datatype=None, name=None, **kwargs):
895 895
896 896 if id is None:
897 897 idReadUnit = self.__getNewId()
898 898 else:
899 899 idReadUnit = str(id)
900 900
901 901 readUnitConfObj = ReadUnitConf()
902 902 readUnitConfObj.setup(idReadUnit, name, datatype, parentId=self.id, **kwargs)
903 903
904 904 self.procUnitConfObjDict[readUnitConfObj.getId()] = readUnitConfObj
905 905
906 906 return readUnitConfObj
907 907
908 908 def addProcUnit(self, inputId='0', datatype=None, name=None):
909 909
910 910 idProcUnit = self.__getNewId()
911 911
912 912 procUnitConfObj = ProcUnitConf()
913 913 procUnitConfObj.setup(idProcUnit, name, datatype, inputId, parentId=self.id)
914 914
915 915 self.procUnitConfObjDict[procUnitConfObj.getId()] = procUnitConfObj
916 916
917 917 return procUnitConfObj
918 918
919 919 def removeProcUnit(self, id):
920 920
921 921 if id in self.procUnitConfObjDict.keys():
922 922 self.procUnitConfObjDict.pop(id)
923 923
924 924 def getReadUnitId(self):
925 925
926 926 readUnitConfObj = self.getReadUnitObj()
927 927
928 928 return readUnitConfObj.id
929 929
930 930 def getReadUnitObj(self):
931 931
932 932 for obj in self.procUnitConfObjDict.values():
933 933 if obj.getElementName() == "ReadUnit":
934 934 return obj
935 935
936 936 return None
937 937
938 938 def getProcUnitObj(self, id=None, name=None):
939 939
940 940 if id != None:
941 941 return self.procUnitConfObjDict[id]
942 942
943 943 if name != None:
944 944 return self.getProcUnitObjByName(name)
945 945
946 946 return None
947 947
948 948 def getProcUnitObjByName(self, name):
949 949
950 950 for obj in self.procUnitConfObjDict.values():
951 951 if obj.name == name:
952 952 return obj
953 953
954 954 return None
955 955
956 956 def procUnitItems(self):
957 957
958 958 return self.procUnitConfObjDict.items()
959 959
960 960 def makeXml(self):
961 961
962 962 projectElement = Element('Project')
963 963 projectElement.set('id', str(self.id))
964 964 projectElement.set('name', self.name)
965 965 projectElement.set('description', self.description)
966 966
967 967 for procUnitConfObj in self.procUnitConfObjDict.values():
968 968 procUnitConfObj.makeXml(projectElement)
969 969
970 970 self.projectElement = projectElement
971 971
972 972 def writeXml(self, filename=None):
973 973
974 974 if filename == None:
975 975 if self.filename:
976 976 filename = self.filename
977 977 else:
978 978 filename = "schain.xml"
979 979
980 980 if not filename:
981 981 print "filename has not been defined. Use setFilename(filename) for do it."
982 982 return 0
983 983
984 984 abs_file = os.path.abspath(filename)
985 985
986 986 if not os.access(os.path.dirname(abs_file), os.W_OK):
987 987 print "No write permission on %s" %os.path.dirname(abs_file)
988 988 return 0
989 989
990 990 if os.path.isfile(abs_file) and not(os.access(abs_file, os.W_OK)):
991 991 print "File %s already exists and it could not be overwriten" %abs_file
992 992 return 0
993 993
994 994 self.makeXml()
995 995
996 996 ElementTree(self.projectElement).write(abs_file, method='xml')
997 997
998 998 self.filename = abs_file
999 999
1000 1000 return 1
1001 1001
1002 1002 def readXml(self, filename = None):
1003
1003
1004 if not filename:
1005 print "filename is not defined"
1006 return 0
1007
1004 1008 abs_file = os.path.abspath(filename)
1005 1009
1006 1010 if not os.path.isfile(abs_file):
1007 print "%s does not exist" %abs_file
1011 print "%s file does not exist" %abs_file
1008 1012 return 0
1009 1013
1010 1014 self.projectElement = None
1011 1015 self.procUnitConfObjDict = {}
1012 1016
1013 1017 try:
1014 1018 self.projectElement = ElementTree().parse(abs_file)
1015 1019 except:
1016 1020 print "Error reading %s, verify file format" %filename
1017 1021 return 0
1018 1022
1019 1023 self.project = self.projectElement.tag
1020 1024
1021 1025 self.id = self.projectElement.get('id')
1022 1026 self.name = self.projectElement.get('name')
1023 1027 self.description = self.projectElement.get('description')
1024 1028
1025 1029 readUnitElementList = self.projectElement.getiterator(ReadUnitConf().getElementName())
1026 1030
1027 1031 for readUnitElement in readUnitElementList:
1028 1032 readUnitConfObj = ReadUnitConf()
1029 1033 readUnitConfObj.readXml(readUnitElement)
1030 1034
1031 1035 if readUnitConfObj.parentId == None:
1032 1036 readUnitConfObj.parentId = self.id
1033 1037
1034 1038 self.procUnitConfObjDict[readUnitConfObj.getId()] = readUnitConfObj
1035 1039
1036 1040 procUnitElementList = self.projectElement.getiterator(ProcUnitConf().getElementName())
1037 1041
1038 1042 for procUnitElement in procUnitElementList:
1039 1043 procUnitConfObj = ProcUnitConf()
1040 1044 procUnitConfObj.readXml(procUnitElement)
1041 1045
1042 1046 if procUnitConfObj.parentId == None:
1043 1047 procUnitConfObj.parentId = self.id
1044 1048
1045 1049 self.procUnitConfObjDict[procUnitConfObj.getId()] = procUnitConfObj
1046 1050
1047 1051 self.filename = abs_file
1048 1052
1049 1053 return 1
1050 1054
1051 1055 def printattr(self):
1052 1056
1053 1057 print "Project[%s]: name = %s, description = %s" %(self.id,
1054 1058 self.name,
1055 1059 self.description)
1056 1060
1057 1061 for procUnitConfObj in self.procUnitConfObjDict.values():
1058 1062 procUnitConfObj.printattr()
1059 1063
1060 1064 def createObjects(self):
1061 1065
1062 1066 for procUnitConfObj in self.procUnitConfObjDict.values():
1063 1067 procUnitConfObj.createObjects(self.plotterQueue)
1064 1068
1065 1069 def __connect(self, objIN, thisObj):
1066 1070
1067 1071 thisObj.setInput(objIN.getOutputObj())
1068 1072
1069 1073 def connectObjects(self):
1070 1074
1071 1075 for thisPUConfObj in self.procUnitConfObjDict.values():
1072 1076
1073 1077 inputId = thisPUConfObj.getInputId()
1074 1078
1075 1079 if int(inputId) == 0:
1076 1080 continue
1077 1081
1078 1082 #Get input object
1079 1083 puConfINObj = self.procUnitConfObjDict[inputId]
1080 1084 puObjIN = puConfINObj.getProcUnitObj()
1081 1085
1082 1086 #Get current object
1083 1087 thisPUObj = thisPUConfObj.getProcUnitObj()
1084 1088
1085 1089 self.__connect(puObjIN, thisPUObj)
1086 1090
1087 1091 def __handleError(self, procUnitConfObj, send_email=True):
1088 1092
1089 1093 import socket
1090 1094
1091 1095 err = traceback.format_exception(sys.exc_info()[0],
1092 1096 sys.exc_info()[1],
1093 1097 sys.exc_info()[2])
1094 1098
1095 1099 print "***** Error occurred in %s *****" %(procUnitConfObj.name)
1096 1100 print "***** %s" %err[-1]
1097 1101
1098 1102 message = "".join(err)
1099 1103
1100 1104 sys.stderr.write(message)
1101 1105
1102 1106 if not send_email:
1103 1107 return
1104 1108
1105 1109 subject = "SChain v%s: Error running %s\n" %(schainpy.__version__, procUnitConfObj.name)
1106 1110
1107 1111 subtitle = "%s: %s\n" %(procUnitConfObj.getElementName() ,procUnitConfObj.name)
1108 1112 subtitle += "Hostname: %s\n" %socket.gethostbyname(socket.gethostname())
1109 1113 subtitle += "Working directory: %s\n" %os.path.abspath("./")
1110 1114 subtitle += "Configuration file: %s\n" %self.filename
1111 1115 subtitle += "Time: %s\n" %str(datetime.datetime.now())
1112 1116
1113 1117 readUnitConfObj = self.getReadUnitObj()
1114 1118 if readUnitConfObj:
1115 1119 subtitle += "\nInput parameters:\n"
1116 1120 subtitle += "[Data path = %s]\n" %readUnitConfObj.path
1117 1121 subtitle += "[Data type = %s]\n" %readUnitConfObj.datatype
1118 1122 subtitle += "[Start date = %s]\n" %readUnitConfObj.startDate
1119 1123 subtitle += "[End date = %s]\n" %readUnitConfObj.endDate
1120 1124 subtitle += "[Start time = %s]\n" %readUnitConfObj.startTime
1121 1125 subtitle += "[End time = %s]\n" %readUnitConfObj.endTime
1122 1126
1123 1127 adminObj = schainpy.admin.SchainNotify()
1124 1128 adminObj.sendAlert(message=message,
1125 1129 subject=subject,
1126 1130 subtitle=subtitle,
1127 1131 filename=self.filename)
1128 1132
1129 1133 def isPaused(self):
1130 1134 return 0
1131 1135
1132 1136 def isStopped(self):
1133 1137 return 0
1134 1138
1135 1139 def runController(self):
1136 1140 """
1137 1141 returns 0 when this process has been stopped, 1 otherwise
1138 1142 """
1139 1143
1140 1144 if self.isPaused():
1141 1145 print "Process suspended"
1142 1146
1143 1147 while True:
1144 1148 sleep(0.1)
1145 1149
1146 1150 if not self.isPaused():
1147 1151 break
1148 1152
1149 1153 if self.isStopped():
1150 1154 break
1151 1155
1152 1156 print "Process reinitialized"
1153 1157
1154 1158 if self.isStopped():
1155 1159 print "Process stopped"
1156 1160 return 0
1157 1161
1158 1162 return 1
1159 1163
1160 1164 def setFilename(self, filename):
1161 1165
1162 1166 self.filename = filename
1163 1167
1164 1168 def setPlotterQueue(self, plotter_queue):
1165 1169
1166 1170 raise NotImplementedError, "Use schainpy.controller_api.ControllerThread instead Project class"
1167 1171
1168 1172 def getPlotterQueue(self):
1169 1173
1170 1174 raise NotImplementedError, "Use schainpy.controller_api.ControllerThread instead Project class"
1171 1175
1172 1176 def useExternalPlotter(self):
1173 1177
1174 1178 raise NotImplementedError, "Use schainpy.controller_api.ControllerThread instead Project class"
1175 1179
1176 1180 def run(self):
1177 1181
1178 1182 print
1179 1183 print "*"*60
1180 1184 print " Starting SIGNAL CHAIN PROCESSING v%s " %schainpy.__version__
1181 1185 print "*"*60
1182 1186 print
1183 1187
1184 1188 keyList = self.procUnitConfObjDict.keys()
1185 1189 keyList.sort()
1186 1190
1187 1191 while(True):
1188 1192
1189 1193 is_ok = False
1190 1194
1191 1195 for procKey in keyList:
1192 1196 # print "Running the '%s' process with %s" %(procUnitConfObj.name, procUnitConfObj.id)
1193 1197
1194 1198 procUnitConfObj = self.procUnitConfObjDict[procKey]
1195 1199
1196 1200 try:
1197 1201 sts = procUnitConfObj.run()
1198 1202 is_ok = is_ok or sts
1199 1203 except KeyboardInterrupt:
1200 1204 is_ok = False
1201 1205 break
1202 1206 except ValueError, e:
1203 1207 sleep(0.5)
1204 1208 self.__handleError(procUnitConfObj, send_email=True)
1205 1209 is_ok = False
1206 1210 break
1207 1211 except:
1208 1212 sleep(0.5)
1209 1213 self.__handleError(procUnitConfObj)
1210 1214 is_ok = False
1211 1215 break
1212 1216
1213 1217 #If every process unit finished so end process
1214 1218 if not(is_ok):
1215 1219 # print "Every process unit have finished"
1216 1220 break
1217 1221
1218 1222 if not self.runController():
1219 1223 break
1220 1224
1221 1225 #Closing every process
1222 1226 for procKey in keyList:
1223 1227 procUnitConfObj = self.procUnitConfObjDict[procKey]
1224 1228 procUnitConfObj.close()
1225 1229
1226 1230 print "Process finished"
1227 1231
1228 1232 def start(self):
1229 1233
1230 1234 self.writeXml()
1231 1235
1232 1236 self.createObjects()
1233 1237 self.connectObjects()
1234 1238 self.run()
1235 1239
1236 1240 if __name__ == '__main__':
1237 1241
1238 1242 desc = "Segundo Test"
1239 1243 filename = "schain.xml"
1240 1244
1241 1245 controllerObj = Project()
1242 1246
1243 1247 controllerObj.setup(id = '191', name='test01', description=desc)
1244 1248
1245 1249 readUnitConfObj = controllerObj.addReadUnit(datatype='Voltage',
1246 1250 path='data/rawdata/',
1247 1251 startDate='2011/01/01',
1248 1252 endDate='2012/12/31',
1249 1253 startTime='00:00:00',
1250 1254 endTime='23:59:59',
1251 1255 online=1,
1252 1256 walk=1)
1253 1257
1254 1258 procUnitConfObj0 = controllerObj.addProcUnit(datatype='Voltage', inputId=readUnitConfObj.getId())
1255 1259
1256 1260 opObj10 = procUnitConfObj0.addOperation(name='selectChannels')
1257 1261 opObj10.addParameter(name='channelList', value='3,4,5', format='intlist')
1258 1262
1259 1263 opObj10 = procUnitConfObj0.addOperation(name='selectHeights')
1260 1264 opObj10.addParameter(name='minHei', value='90', format='float')
1261 1265 opObj10.addParameter(name='maxHei', value='180', format='float')
1262 1266
1263 1267 opObj12 = procUnitConfObj0.addOperation(name='CohInt', optype='external')
1264 1268 opObj12.addParameter(name='n', value='10', format='int')
1265 1269
1266 1270 procUnitConfObj1 = controllerObj.addProcUnit(datatype='Spectra', inputId=procUnitConfObj0.getId())
1267 1271 procUnitConfObj1.addParameter(name='nFFTPoints', value='32', format='int')
1268 1272 # procUnitConfObj1.addParameter(name='pairList', value='(0,1),(0,2),(1,2)', format='')
1269 1273
1270 1274
1271 1275 opObj11 = procUnitConfObj1.addOperation(name='SpectraPlot', optype='external')
1272 1276 opObj11.addParameter(name='idfigure', value='1', format='int')
1273 1277 opObj11.addParameter(name='wintitle', value='SpectraPlot0', format='str')
1274 1278 opObj11.addParameter(name='zmin', value='40', format='int')
1275 1279 opObj11.addParameter(name='zmax', value='90', format='int')
1276 1280 opObj11.addParameter(name='showprofile', value='1', format='int')
1277 1281
1278 1282 print "Escribiendo el archivo XML"
1279 1283
1280 1284 controllerObj.writeXml(filename)
1281 1285
1282 1286 print "Leyendo el archivo XML"
1283 1287 controllerObj.readXml(filename)
1284 1288 #controllerObj.printattr()
1285 1289
1286 1290 controllerObj.createObjects()
1287 1291 controllerObj.connectObjects()
1288 1292 controllerObj.run()
1289 1293
1290 1294 No newline at end of file
@@ -1,41 +1,40
1 1 '''
2 2 Created on Jul 16, 2014
3 3
4 4 @author: @author: Miguel Urco
5 5 '''
6 6
7 7 from schainpy import __version__
8 8 from setuptools import setup, Extension
9 9
10 10 setup(name="schainpy",
11 11 version=__version__,
12 12 description="Python tools to read, write and process Jicamarca data",
13 13 author="Miguel Urco",
14 14 author_email="miguel.urco@jro.igp.gob.pe",
15 15 url="http://jro.igp.gob.pe",
16 16 packages = {'schainpy',
17 17 'schainpy.model',
18 18 'schainpy.model.data',
19 19 'schainpy.model.graphics',
20 20 'schainpy.model.io',
21 21 'schainpy.model.proc',
22 22 'schainpy.model.serializer',
23 23 'schainpy.model.utils',
24 24 'schainpy.gui',
25 25 'schainpy.gui.figures',
26 26 'schainpy.gui.viewcontroller',
27 27 'schainpy.gui.viewer',
28 28 'schainpy.gui.viewer.windows'},
29 29 py_modules=[''],
30 30 package_data={'': ['schain.conf.template'],
31 31 'schainpy.gui.figures': ['*.png','*.jpg'],
32 32 },
33 33 include_package_data=False,
34 scripts =['schainpy/gui/schainGUI'],
34 scripts =['schainpy/gui/schainGUI',
35 'schainpy/scripts/schain'],
35 36 install_requires=["numpy >= 1.6.0",
36 37 "scipy >= 0.9.0",
37 "h5py >= 2.0.1",
38 38 "matplotlib >= 1.0.0",
39 # "pyfits >= 2.0.0",
40 39 ],
41 40 ) No newline at end of file
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now