##// END OF EJS Templates
Now alarm is a process, add SchainWarning exception for handling non-stop exceptions
jespinoza -
r1129:986a05a7d203
parent child
Show More
@@ -1,477 +1,498
1 1 """The admin module contains all administrative classes relating to the schain python api.
2 2
3 3 The main role of this module is to send some reports. It contains a
4 4 notification class and a standard error handing class.
5 5
6 6 $Id: admin.py 3966 2015-12-01 14:32:29Z miguel.urco $
7 7 """
8 8 import os
9 9 import sys
10 10 import time
11 11 import traceback
12 12 import smtplib
13 13 import ConfigParser
14 14 import StringIO
15 15 from threading import Thread
16 from multiprocessing import Process
16 17 from email.mime.text import MIMEText
17 18 from email.mime.application import MIMEApplication
18 19 from email.mime.multipart import MIMEMultipart
19 20
20 21 from schainpy.utils import log
21 22 from schainpy.model.graphics.jroplot_data import popup
22 23
23 24 def get_path():
24 25 '''
25 26 Return schainpy path
26 27 '''
27 28
28 29 try:
29 30 root = __file__
30 31 if os.path.islink(root):
31 32 root = os.path.realpath(root)
32 33
33 34 return os.path.dirname(os.path.abspath(root))
34 35 except:
35 36 log.error('I am sorry, but something is wrong... __file__ not found')
36 37
37 def alarm(modes=[1], **kwargs):
38 class Alarm(Process):
38 39 '''
39 40 modes:
40 41 0 - All
41 42 1 - Sound alarm
42 43 2 - Send email
43 44 3 - Popup message
44 45 4 - Send to alarm system TODO
45 46 '''
46 47
48 def __init__(self, modes=[1], **kwargs):
49 Process.__init__(self)
50 self.modes = modes
51 self.kwargs = kwargs
52
53 @staticmethod
47 54 def play_sound():
48 55 sound = os.path.join(get_path(), 'alarm1.oga')
49 56 if os.path.exists(sound):
50 57 for __ in range(2):
51 58 os.system('paplay {}'.format(sound))
52 59 time.sleep(0.5)
53 60 else:
54 61 log.warning('Unable to play alarm, sound file not found', 'ADMIN')
55 62
63 @staticmethod
56 64 def send_email(**kwargs):
57 65 notifier = SchainNotify()
58 66 notifier.notify(**kwargs)
59 67
68 @staticmethod
60 69 def show_popup(message='Error'):
61 70 popup(message)
62 71
72 @staticmethod
63 73 def send_alarm():
64 74 pass
65 75
76 @staticmethod
66 77 def get_kwargs(kwargs, keys):
67 78 ret = {}
68 79 for key in keys:
69 80 ret[key] = kwargs[key]
70 81 return ret
71 82
72 tasks = {
73 1 : send_email,
74 2 : play_sound,
75 3 : show_popup,
76 4 : send_alarm,
77 }
78
79 tasks_args = {
80 1: ['email', 'message', 'subject', 'subtitle', 'filename'],
81 2: [],
82 3: ['message'],
83 4: [],
84 }
85
86 for mode in modes:
87 if mode == 0:
88 for x in tasks:
89 t = Thread(target=tasks[x], kwargs=get_kwargs(kwargs, tasks_args[x]))
83 def run(self):
84 tasks = {
85 1 : self.send_email,
86 2 : self.play_sound,
87 3 : self.show_popup,
88 4 : self.send_alarm,
89 }
90
91 tasks_args = {
92 1: ['email', 'message', 'subject', 'subtitle', 'filename'],
93 2: [],
94 3: ['message'],
95 4: [],
96 }
97 procs = []
98 for mode in self.modes:
99 if 0 in self.modes:
100 for x in tasks:
101 t = Thread(target=tasks[x], kwargs=self.get_kwargs(self.kwargs, tasks_args[x]))
102 t.start()
103 procs.append(t)
104 break
105 else:
106 t = Thread(target=tasks[mode], kwargs=self.get_kwargs(self.kwargs, tasks_args[mode]))
90 107 t.start()
91 break
92 else:
93 t = Thread(target=tasks[mode], kwargs=get_kwargs(kwargs, tasks_args[x]))
94 t.start()
108 procs.append(t)
109 for t in procs:
110 t.join()
95 111
96 112
97 113 class SchainConfigure():
98 114
99 115 __DEFAULT_ADMINISTRATOR_EMAIL = "juan.espinoza@jro.igp.gob.pe"
100 116 __DEFAULT_EMAIL_SERVER = "jro-zimbra.igp.gob.pe"
101 117 __DEFAULT_SENDER_EMAIL = "notifier-schain@jro.igp.gob.pe"
102 118 __DEFAULT_SENDER_PASS = ""
103 119
104 120 __SCHAIN_ADMINISTRATOR_EMAIL = "CONTACT"
105 121 __SCHAIN_EMAIL_SERVER = "MAILSERVER"
106 122 __SCHAIN_SENDER_EMAIL = "MAILSERVER_ACCOUNT"
107 123 __SCHAIN_SENDER_PASS = "MAILSERVER_PASSWORD"
108 124
109 125 def __init__(self, initFile = None):
110 126
111 127 # Set configuration file
112 128 if (initFile == None):
113 129 self.__confFilePath = "/etc/schain.conf"
114 130 else:
115 131 self.__confFilePath = initFile
116 132
117 133 # open configuration file
118 134 try:
119 135 self.__confFile = open(self.__confFilePath, "r")
120 136 except IOError:
121 137 # can't read from file - use all hard-coded values
122 138 self.__initFromHardCode()
123 139 return
124 140
125 141 # create Parser using standard module ConfigParser
126 142 self.__parser = ConfigParser.ConfigParser()
127 143
128 144 # read conf file into a StringIO with "[madrigal]\n" section heading prepended
129 145 strConfFile = StringIO.StringIO("[schain]\n" + self.__confFile.read())
130 146
131 147 # parse StringIO configuration file
132 148 self.__parser.readfp(strConfFile)
133 149
134 150 # read information from configuration file
135 151 self.__readConfFile()
136 152
137 153 # close conf file
138 154 self.__confFile.close()
139 155
140 156
141 157 def __initFromHardCode(self):
142 158
143 159 self.__sender_email = self.__DEFAULT_SENDER_EMAIL
144 160 self.__sender_pass = self.__DEFAULT_SENDER_PASS
145 161 self.__admin_email = self.__DEFAULT_ADMINISTRATOR_EMAIL
146 162 self.__email_server = self.__DEFAULT_EMAIL_SERVER
147 163
148 164 def __readConfFile(self):
149 165 """__readConfFile is a private helper function that reads information from the parsed config file.
150 166
151 167 Inputs: None
152 168
153 169 Returns: Void.
154 170
155 171 Affects: Initializes class member variables that are found in the config file.
156 172
157 173 Exceptions: MadrigalError thrown if any key not found.
158 174 """
159 175
160 176 # get the sender email
161 177 try:
162 178 self.__sender_email = self.__parser.get("schain", self.__SCHAIN_SENDER_EMAIL)
163 179 except:
164 180 self.__sender_email = self.__DEFAULT_SENDER_EMAIL
165 181
166 182 # get the sender password
167 183 try:
168 184 self.__sender_pass = self.__parser.get("schain", self.__SCHAIN_SENDER_PASS)
169 185 except:
170 186 self.__sender_pass = self.__DEFAULT_SENDER_PASS
171 187
172 188 # get the administrator email
173 189 try:
174 190 self.__admin_email = self.__parser.get("schain", self.__SCHAIN_ADMINISTRATOR_EMAIL)
175 191 except:
176 192 self.__admin_email = self.__DEFAULT_ADMINISTRATOR_EMAIL
177 193
178 194 # get the server email
179 195 try:
180 196 self.__email_server = self.__parser.get("schain", self.__SCHAIN_EMAIL_SERVER)
181 197 except:
182 198 self.__email_server = self.__DEFAULT_EMAIL_SERVER
183 199
184 200 def getEmailServer(self):
185 201
186 202 return self.__email_server
187 203
188 204 def getSenderEmail(self):
189 205
190 206 return self.__sender_email
191 207
192 208 def getSenderPass(self):
193 209
194 210 return self.__sender_pass
195 211
196 212 def getAdminEmail(self):
197 213
198 214 return self.__admin_email
199 215
200 216 class SchainNotify:
201 217 """SchainNotify is an object used to send messages to an administrator about a Schain software.
202 218
203 219 This object provides functions needed to send messages to an administrator about a Schain , for now
204 220 only sendAlert, which sends an email to the site administrator found is ADMIN_EMAIL
205 221
206 222 Usage example:
207 223
208 224 import schainpy.admin
209 225
210 226 try:
211 227
212 228 adminObj = schainpy.admin.SchainNotify()
213 229 adminObj.sendAlert('This is important!', 'Important Message')
214 230
215 231 except schainpy.admin.SchainError, e:
216 232
217 233 print e.getExceptionStr()
218 234
219 235
220 236 Non-standard Python modules used:
221 237 None
222 238
223 239 Exceptions thrown: None - Note that SchainNotify tries every trick it knows to avoid
224 240 throwing exceptions, since this is the class that will generally be called when there is a problem.
225 241
226 242 Change history:
227 243
228 244 Written by "Miguel Urco":mailto:miguel.urco@jro.igp.gob.pe Dec. 1, 2015
229 245 """
230 246
231 247 #constants
232 248
233 249 def __init__(self):
234 250 """__init__ initializes SchainNotify by getting some basic information from SchainDB and SchainSite.
235 251
236 252 Note that SchainNotify tries every trick it knows to avoid throwing exceptions, since
237 253 this is the class that will generally be called when there is a problem.
238 254
239 255 Inputs: Existing SchainDB object, by default = None.
240 256
241 257 Returns: void
242 258
243 259 Affects: Initializes self.__binDir.
244 260
245 261 Exceptions: None.
246 262 """
247 263
248 264 # note that the main configuration file is unavailable
249 265 # the best that can be done is send an email to root using localhost mailserver
250 266 confObj = SchainConfigure()
251 267
252 268 self.__emailFromAddress = confObj.getSenderEmail()
253 269 self.__emailPass = confObj.getSenderPass()
254 270 self.__emailToAddress = confObj.getAdminEmail()
255 271 self.__emailServer = confObj.getEmailServer()
256 272
257 273 def sendEmail(self, email_from, email_to, subject='Error running ...', message="", subtitle="", filename="", html_format=True):
258
274
259 275 if not email_to:
260 276 return 0
261 277
262 278 if not self.__emailServer:
263 279 return 0
264 280
281 log.success('Sending email to {}...'.format(email_to), 'System')
282
265 283 msg = MIMEMultipart()
266 284 msg['Subject'] = subject
267 285 msg['From'] = "(Python SChain API): " + email_from
268 286 msg['Reply-to'] = email_from
269 287 msg['To'] = email_to
270 288
271 289 # That is what u see if dont have an email reader:
272 290 msg.preamble = 'SChainPy'
273 291
274 292 if html_format:
275 293 message = "<h1> %s </h1>" %subject + "<h3>" + subtitle.replace("\n", "</h3><h3>\n") + "</h3>" + message.replace("\n", "<br>\n")
276 294 message = "<html>\n" + message + '</html>'
277 295
278 296 # This is the textual part:
279 297 part = MIMEText(message, "html")
280 298 else:
281 299 message = subject + "\n" + subtitle + "\n" + message
282 300 part = MIMEText(message)
283 301
284 302 msg.attach(part)
285 303
286 304 if filename and os.path.isfile(filename):
287 305 # This is the binary part(The Attachment):
288 306 part = MIMEApplication(open(filename,"rb").read())
289 307 part.add_header('Content-Disposition',
290 308 'attachment',
291 309 filename=os.path.basename(filename))
292 310 msg.attach(part)
293 311
294 312 # Create an instance in SMTP server
295 313 try:
296 314 smtp = smtplib.SMTP(self.__emailServer)
297 315 except:
298 print "***** Could not connect to server %s *****" %self.__emailServer
316 log.error('Could not connect to server {}'.format(self.__emailServer), 'System')
299 317 return 0
300 318
301 319 # Start the server:
302 320 # smtp.ehlo()
303 321 if self.__emailPass:
304 322 smtp.login(self.__emailFromAddress, self.__emailPass)
305 323
306 324 # Send the email
307 325 try:
308 326 smtp.sendmail(msg['From'], msg['To'], msg.as_string())
309 327 except:
310 print "***** Could not send the email to %s *****" %msg['To']
328 log.error('Could not send the email to {}'.format(msg['To']), 'System')
311 329 smtp.quit()
312 330 return 0
313 331
314 332 smtp.quit()
333
334 log.success('Email sent ', 'System')
315 335
316 336 return 1
317 337
318 338 def sendAlert(self, message, subject = "", subtitle="", filename=""):
319 339 """sendAlert sends an email with the given message and optional title.
320 340
321 341 Inputs: message (string), and optional title (string)
322 342
323 343 Returns: void
324 344
325 345 Affects: none
326 346
327 347 Exceptions: None.
328 348 """
329 349
330 350 if not self.__emailToAddress:
331 351 return 0
332 352
333 353 print "***** Sending alert to %s *****" %self.__emailToAddress
334 354 # set up message
335 355
336 356 sent=self.sendEmail(email_from=self.__emailFromAddress,
337 357 email_to=self.__emailToAddress,
338 358 subject=subject,
339 359 message=message,
340 360 subtitle=subtitle,
341 361 filename=filename)
342 362
343 363 if not sent:
344 364 return 0
345 365
346 print "***** Your system administrator has been notified *****"
347
348 366 return 1
349 367
350 368 def notify(self, email, message, subject = "", subtitle="", filename=""):
351 369 """notify sends an email with the given message and title to email.
352 370
353 371 Inputs: email (string), message (string), and subject (string)
354 372
355 373 Returns: void
356 374
357 375 Affects: none
358 376
359 377 Exceptions: None.
360 378 """
361 379
362 380 if email is None:
363 381 email = self.__emailToAddress
364
365 log.success('Notifying to %s ...'.format(email), 'ADMIN')
366
367 self.sendEmail(email_from=self.__emailFromAddress,
368 email_to=email,
369 subject=subject,
370 message=message,
371 subtitle=subtitle,
372 filename=filename)
373 382
374 log.success('Email sent', 'ADMIN')
383 self.sendEmail(
384 email_from=self.__emailFromAddress,
385 email_to=email,
386 subject=subject,
387 message=message,
388 subtitle=subtitle,
389 filename=filename
390 )
391
375 392
376 393 class SchainError(Exception):
377 394 """SchainError is an exception class that is thrown for all known errors using Schain Py lib.
378 395
379 396 Usage example:
380 397
381 398 import sys, traceback
382 399 import schainpy.admin
383 400
384 401 try:
385 402
386 403 test = open('ImportantFile.txt', 'r')
387 404
388 405 except:
389 406
390 407 raise schainpy.admin.SchainError('ImportantFile.txt not opened!',
391 408 traceback.format_exception(sys.exc_info()[0],
392 409 sys.exc_info()[1],
393 410 sys.exc_info()[2]))
394 411 """
395 412
396 413
397 414 def __init__(self, strInterpretation, exceptionList=None):
398 415 """ __init__ gathers the interpretation string along with all information from sys.exc_info().
399 416
400 417 Inputs:
401 418 strIntepretation - A string representing the programmer's interpretation of
402 419 why the exception occurred
403 420
404 421 exceptionList - a list of strings completely describing the exception.
405 422 Generated by traceback.format_exception(sys.exc_info()[0],
406 423 sys.exc_info()[1],
407 424 sys.exc_info()[2])
408 425
409 426 Returns: Void.
410 427
411 428 Affects: Initializes class member variables _strInterp, _strExcList.
412 429
413 430 Exceptions: None.
414 431 """
415 432
416 433 if not exceptionList:
417 434 exceptionList = traceback.format_exception(sys.exc_info()[0],
418 435 sys.exc_info()[1],
419 436 sys.exc_info()[2])
420 437
421 438 self._strInterp = strInterpretation
422 439 self._strExcList = exceptionList
423 440
424 441
425 442 def getExceptionStr(self):
426 443 """ getExceptionStr returns a formatted string ready for printing completely describing the exception.
427 444
428 445 Inputs: None
429 446
430 447 Returns: A formatted string ready for printing completely describing the exception.
431 448
432 449 Affects: None
433 450
434 451 Exceptions: None.
435 452 """
436 453 excStr = ''
437 454 excStr = excStr + self._strInterp + '\n\n'
438 455
439 456 if self._strExcList != None:
440 457 for item in self._strExcList:
441 458 excStr = excStr + str(item) + '\n'
442 459
443 460 return excStr
444 461
445 462 def __str__(self):
446 463
447 464 return(self.getExceptionStr())
448 465
449 466
450 467 def getExceptionHtml(self):
451 468 """ getExceptionHtml returns an Html formatted string completely describing the exception.
452 469
453 470 Inputs: None
454 471
455 472 Returns: A formatted string ready for printing completely describing the exception.
456 473
457 474 Affects: None
458 475
459 476 Exceptions: None.
460 477 """
461 478
462 479 excStr = '<BR>The following Schain Python exception has occurred:\n<BR>'
463 480 excStr = excStr + self._strInterp + '\n<BR>\n'
464 481
465 482 if self._strExcList != None:
466 483 for item in self._strExcList:
467 484 excStr = excStr + str(item) + '\n<BR>'
468 485
469 486 return excStr
470 487
488 class SchainWarning(Exception):
489 pass
490
491
471 492 if __name__ == '__main__':
472 493
473 494 test = SchainNotify()
474 495
475 496 test.sendAlert('This is a message from the python module SchainNotify', 'Test from SchainNotify')
476 497
477 498 print 'Hopefully message sent - check.'
@@ -1,1324 +1,1330
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 math
11 11 import time
12 12 from multiprocessing import Process, cpu_count
13 13
14 14 from xml.etree.ElementTree import ElementTree, Element, SubElement, tostring
15 15 from xml.dom import minidom
16 16
17 17 import schainpy
18 import schainpy.admin
18 from schainpy.admin import Alarm, SchainWarning
19 19 from schainpy.model import *
20 20 from schainpy.utils import log
21 21
22 22 DTYPES = {
23 23 'Voltage': '.r',
24 24 'Spectra': '.pdata'
25 25 }
26 26
27 27
28 28 def MPProject(project, n=cpu_count()):
29 29 '''
30 30 Project wrapper to run schain in n processes
31 31 '''
32 32
33 33 rconf = project.getReadUnitObj()
34 34 op = rconf.getOperationObj('run')
35 35 dt1 = op.getParameterValue('startDate')
36 36 dt2 = op.getParameterValue('endDate')
37 37 tm1 = op.getParameterValue('startTime')
38 38 tm2 = op.getParameterValue('endTime')
39 39 days = (dt2 - dt1).days
40 40
41 41 for day in range(days + 1):
42 42 skip = 0
43 43 cursor = 0
44 44 processes = []
45 45 dt = dt1 + datetime.timedelta(day)
46 46 dt_str = dt.strftime('%Y/%m/%d')
47 47 reader = JRODataReader()
48 48 paths, files = reader.searchFilesOffLine(path=rconf.path,
49 49 startDate=dt,
50 50 endDate=dt,
51 51 startTime=tm1,
52 52 endTime=tm2,
53 53 ext=DTYPES[rconf.datatype])
54 54 nFiles = len(files)
55 55 if nFiles == 0:
56 56 continue
57 57 skip = int(math.ceil(nFiles / n))
58 58 while nFiles > cursor * skip:
59 59 rconf.update(startDate=dt_str, endDate=dt_str, cursor=cursor,
60 60 skip=skip)
61 61 p = project.clone()
62 62 p.start()
63 63 processes.append(p)
64 64 cursor += 1
65 65
66 66 def beforeExit(exctype, value, trace):
67 67 for process in processes:
68 68 process.terminate()
69 69 process.join()
70 70 print traceback.print_tb(trace)
71 71
72 72 sys.excepthook = beforeExit
73 73
74 74 for process in processes:
75 75 process.join()
76 76 process.terminate()
77 77
78 78 time.sleep(3)
79 79
80 80
81 81 class ParameterConf():
82 82
83 83 id = None
84 84 name = None
85 85 value = None
86 86 format = None
87 87
88 88 __formated_value = None
89 89
90 90 ELEMENTNAME = 'Parameter'
91 91
92 92 def __init__(self):
93 93
94 94 self.format = 'str'
95 95
96 96 def getElementName(self):
97 97
98 98 return self.ELEMENTNAME
99 99
100 100 def getValue(self):
101 101
102 102 value = self.value
103 103 format = self.format
104 104
105 105 if self.__formated_value != None:
106 106
107 107 return self.__formated_value
108 108
109 109 if format == 'obj':
110 110 return value
111 111
112 112 if format == 'str':
113 113 self.__formated_value = str(value)
114 114 return self.__formated_value
115 115
116 116 if value == '':
117 117 raise ValueError, '%s: This parameter value is empty' % self.name
118 118
119 119 if format == 'list':
120 120 strList = value.split(',')
121 121
122 122 self.__formated_value = strList
123 123
124 124 return self.__formated_value
125 125
126 126 if format == 'intlist':
127 127 '''
128 128 Example:
129 129 value = (0,1,2)
130 130 '''
131 131
132 132 new_value = ast.literal_eval(value)
133 133
134 134 if type(new_value) not in (tuple, list):
135 135 new_value = [int(new_value)]
136 136
137 137 self.__formated_value = new_value
138 138
139 139 return self.__formated_value
140 140
141 141 if format == 'floatlist':
142 142 '''
143 143 Example:
144 144 value = (0.5, 1.4, 2.7)
145 145 '''
146 146
147 147 new_value = ast.literal_eval(value)
148 148
149 149 if type(new_value) not in (tuple, list):
150 150 new_value = [float(new_value)]
151 151
152 152 self.__formated_value = new_value
153 153
154 154 return self.__formated_value
155 155
156 156 if format == 'date':
157 157 strList = value.split('/')
158 158 intList = [int(x) for x in strList]
159 159 date = datetime.date(intList[0], intList[1], intList[2])
160 160
161 161 self.__formated_value = date
162 162
163 163 return self.__formated_value
164 164
165 165 if format == 'time':
166 166 strList = value.split(':')
167 167 intList = [int(x) for x in strList]
168 168 time = datetime.time(intList[0], intList[1], intList[2])
169 169
170 170 self.__formated_value = time
171 171
172 172 return self.__formated_value
173 173
174 174 if format == 'pairslist':
175 175 '''
176 176 Example:
177 177 value = (0,1),(1,2)
178 178 '''
179 179
180 180 new_value = ast.literal_eval(value)
181 181
182 182 if type(new_value) not in (tuple, list):
183 183 raise ValueError, '%s has to be a tuple or list of pairs' % value
184 184
185 185 if type(new_value[0]) not in (tuple, list):
186 186 if len(new_value) != 2:
187 187 raise ValueError, '%s has to be a tuple or list of pairs' % value
188 188 new_value = [new_value]
189 189
190 190 for thisPair in new_value:
191 191 if len(thisPair) != 2:
192 192 raise ValueError, '%s has to be a tuple or list of pairs' % value
193 193
194 194 self.__formated_value = new_value
195 195
196 196 return self.__formated_value
197 197
198 198 if format == 'multilist':
199 199 '''
200 200 Example:
201 201 value = (0,1,2),(3,4,5)
202 202 '''
203 203 multiList = ast.literal_eval(value)
204 204
205 205 if type(multiList[0]) == int:
206 206 multiList = ast.literal_eval('(' + value + ')')
207 207
208 208 self.__formated_value = multiList
209 209
210 210 return self.__formated_value
211 211
212 212 if format == 'bool':
213 213 value = int(value)
214 214
215 215 if format == 'int':
216 216 value = float(value)
217 217
218 218 format_func = eval(format)
219 219
220 220 self.__formated_value = format_func(value)
221 221
222 222 return self.__formated_value
223 223
224 224 def updateId(self, new_id):
225 225
226 226 self.id = str(new_id)
227 227
228 228 def setup(self, id, name, value, format='str'):
229 229 self.id = str(id)
230 230 self.name = name
231 231 if format == 'obj':
232 232 self.value = value
233 233 else:
234 234 self.value = str(value)
235 235 self.format = str.lower(format)
236 236
237 237 self.getValue()
238 238
239 239 return 1
240 240
241 241 def update(self, name, value, format='str'):
242 242
243 243 self.name = name
244 244 self.value = str(value)
245 245 self.format = format
246 246
247 247 def makeXml(self, opElement):
248 248 if self.name not in ('queue',):
249 249 parmElement = SubElement(opElement, self.ELEMENTNAME)
250 250 parmElement.set('id', str(self.id))
251 251 parmElement.set('name', self.name)
252 252 parmElement.set('value', self.value)
253 253 parmElement.set('format', self.format)
254 254
255 255 def readXml(self, parmElement):
256 256
257 257 self.id = parmElement.get('id')
258 258 self.name = parmElement.get('name')
259 259 self.value = parmElement.get('value')
260 260 self.format = str.lower(parmElement.get('format'))
261 261
262 262 # Compatible with old signal chain version
263 263 if self.format == 'int' and self.name == 'idfigure':
264 264 self.name = 'id'
265 265
266 266 def printattr(self):
267 267
268 268 print 'Parameter[%s]: name = %s, value = %s, format = %s' % (self.id, self.name, self.value, self.format)
269 269
270 270
271 271 class OperationConf():
272 272
273 273 id = None
274 274 name = None
275 275 priority = None
276 276 type = None
277 277
278 278 parmConfObjList = []
279 279
280 280 ELEMENTNAME = 'Operation'
281 281
282 282 def __init__(self):
283 283
284 284 self.id = '0'
285 285 self.name = None
286 286 self.priority = None
287 287 self.type = 'self'
288 288
289 289 def __getNewId(self):
290 290
291 291 return int(self.id) * 10 + len(self.parmConfObjList) + 1
292 292
293 293 def updateId(self, new_id):
294 294
295 295 self.id = str(new_id)
296 296
297 297 n = 1
298 298 for parmObj in self.parmConfObjList:
299 299
300 300 idParm = str(int(new_id) * 10 + n)
301 301 parmObj.updateId(idParm)
302 302
303 303 n += 1
304 304
305 305 def getElementName(self):
306 306
307 307 return self.ELEMENTNAME
308 308
309 309 def getParameterObjList(self):
310 310
311 311 return self.parmConfObjList
312 312
313 313 def getParameterObj(self, parameterName):
314 314
315 315 for parmConfObj in self.parmConfObjList:
316 316
317 317 if parmConfObj.name != parameterName:
318 318 continue
319 319
320 320 return parmConfObj
321 321
322 322 return None
323 323
324 324 def getParameterObjfromValue(self, parameterValue):
325 325
326 326 for parmConfObj in self.parmConfObjList:
327 327
328 328 if parmConfObj.getValue() != parameterValue:
329 329 continue
330 330
331 331 return parmConfObj.getValue()
332 332
333 333 return None
334 334
335 335 def getParameterValue(self, parameterName):
336 336
337 337 parameterObj = self.getParameterObj(parameterName)
338 338
339 339 # if not parameterObj:
340 340 # return None
341 341
342 342 value = parameterObj.getValue()
343 343
344 344 return value
345 345
346 346 def getKwargs(self):
347 347
348 348 kwargs = {}
349 349
350 350 for parmConfObj in self.parmConfObjList:
351 351 if self.name == 'run' and parmConfObj.name == 'datatype':
352 352 continue
353 353
354 354 kwargs[parmConfObj.name] = parmConfObj.getValue()
355 355
356 356 return kwargs
357 357
358 358 def setup(self, id, name, priority, type):
359 359
360 360 self.id = str(id)
361 361 self.name = name
362 362 self.type = type
363 363 self.priority = priority
364 364
365 365 self.parmConfObjList = []
366 366
367 367 def removeParameters(self):
368 368
369 369 for obj in self.parmConfObjList:
370 370 del obj
371 371
372 372 self.parmConfObjList = []
373 373
374 374 def addParameter(self, name, value, format='str'):
375 375
376 376 if value is None:
377 377 return None
378 378 id = self.__getNewId()
379 379
380 380 parmConfObj = ParameterConf()
381 381 if not parmConfObj.setup(id, name, value, format):
382 382 return None
383 383
384 384 self.parmConfObjList.append(parmConfObj)
385 385
386 386 return parmConfObj
387 387
388 388 def changeParameter(self, name, value, format='str'):
389 389
390 390 parmConfObj = self.getParameterObj(name)
391 391 parmConfObj.update(name, value, format)
392 392
393 393 return parmConfObj
394 394
395 395 def makeXml(self, procUnitElement):
396 396
397 397 opElement = SubElement(procUnitElement, self.ELEMENTNAME)
398 398 opElement.set('id', str(self.id))
399 399 opElement.set('name', self.name)
400 400 opElement.set('type', self.type)
401 401 opElement.set('priority', str(self.priority))
402 402
403 403 for parmConfObj in self.parmConfObjList:
404 404 parmConfObj.makeXml(opElement)
405 405
406 406 def readXml(self, opElement):
407 407
408 408 self.id = opElement.get('id')
409 409 self.name = opElement.get('name')
410 410 self.type = opElement.get('type')
411 411 self.priority = opElement.get('priority')
412 412
413 413 # Compatible with old signal chain version
414 414 # Use of 'run' method instead 'init'
415 415 if self.type == 'self' and self.name == 'init':
416 416 self.name = 'run'
417 417
418 418 self.parmConfObjList = []
419 419
420 420 parmElementList = opElement.iter(ParameterConf().getElementName())
421 421
422 422 for parmElement in parmElementList:
423 423 parmConfObj = ParameterConf()
424 424 parmConfObj.readXml(parmElement)
425 425
426 426 # Compatible with old signal chain version
427 427 # If an 'plot' OPERATION is found, changes name operation by the value of its type PARAMETER
428 428 if self.type != 'self' and self.name == 'Plot':
429 429 if parmConfObj.format == 'str' and parmConfObj.name == 'type':
430 430 self.name = parmConfObj.value
431 431 continue
432 432
433 433 self.parmConfObjList.append(parmConfObj)
434 434
435 435 def printattr(self):
436 436
437 437 print '%s[%s]: name = %s, type = %s, priority = %s' % (self.ELEMENTNAME,
438 438 self.id,
439 439 self.name,
440 440 self.type,
441 441 self.priority)
442 442
443 443 for parmConfObj in self.parmConfObjList:
444 444 parmConfObj.printattr()
445 445
446 446 def createObject(self, plotter_queue=None):
447 447
448 448 if self.type == 'self':
449 449 raise ValueError, 'This operation type cannot be created'
450 450
451 451 if self.type == 'plotter':
452 452 if not plotter_queue:
453 453 raise ValueError, 'plotter_queue is not defined. Use:\nmyProject = Project()\nmyProject.setPlotterQueue(plotter_queue)'
454 454
455 455 opObj = Plotter(self.name, plotter_queue)
456 456
457 457 if self.type == 'external' or self.type == 'other':
458 458
459 459 className = eval(self.name)
460 460 kwargs = self.getKwargs()
461 461
462 462 opObj = className(**kwargs)
463 463
464 464 return opObj
465 465
466 466
467 467 class ProcUnitConf():
468 468
469 469 id = None
470 470 name = None
471 471 datatype = None
472 472 inputId = None
473 473 parentId = None
474 474
475 475 opConfObjList = []
476 476
477 477 procUnitObj = None
478 478 opObjList = []
479 479
480 480 ELEMENTNAME = 'ProcUnit'
481 481
482 482 def __init__(self):
483 483
484 484 self.id = None
485 485 self.datatype = None
486 486 self.name = None
487 487 self.inputId = None
488 488
489 489 self.opConfObjList = []
490 490
491 491 self.procUnitObj = None
492 492 self.opObjDict = {}
493 493
494 494 def __getPriority(self):
495 495
496 496 return len(self.opConfObjList) + 1
497 497
498 498 def __getNewId(self):
499 499
500 500 return int(self.id) * 10 + len(self.opConfObjList) + 1
501 501
502 502 def getElementName(self):
503 503
504 504 return self.ELEMENTNAME
505 505
506 506 def getId(self):
507 507
508 508 return self.id
509 509
510 510 def updateId(self, new_id, parentId=parentId):
511 511
512 512 new_id = int(parentId) * 10 + (int(self.id) % 10)
513 513 new_inputId = int(parentId) * 10 + (int(self.inputId) % 10)
514 514
515 515 # If this proc unit has not inputs
516 516 if self.inputId == '0':
517 517 new_inputId = 0
518 518
519 519 n = 1
520 520 for opConfObj in self.opConfObjList:
521 521
522 522 idOp = str(int(new_id) * 10 + n)
523 523 opConfObj.updateId(idOp)
524 524
525 525 n += 1
526 526
527 527 self.parentId = str(parentId)
528 528 self.id = str(new_id)
529 529 self.inputId = str(new_inputId)
530 530
531 531 def getInputId(self):
532 532
533 533 return self.inputId
534 534
535 535 def getOperationObjList(self):
536 536
537 537 return self.opConfObjList
538 538
539 539 def getOperationObj(self, name=None):
540 540
541 541 for opConfObj in self.opConfObjList:
542 542
543 543 if opConfObj.name != name:
544 544 continue
545 545
546 546 return opConfObj
547 547
548 548 return None
549 549
550 550 def getOpObjfromParamValue(self, value=None):
551 551
552 552 for opConfObj in self.opConfObjList:
553 553 if opConfObj.getParameterObjfromValue(parameterValue=value) != value:
554 554 continue
555 555 return opConfObj
556 556 return None
557 557
558 558 def getProcUnitObj(self):
559 559
560 560 return self.procUnitObj
561 561
562 562 def setup(self, id, name, datatype, inputId, parentId=None):
563 563
564 564 # Compatible with old signal chain version
565 565 if datatype == None and name == None:
566 566 raise ValueError, 'datatype or name should be defined'
567 567
568 568 if name == None:
569 569 if 'Proc' in datatype:
570 570 name = datatype
571 571 else:
572 572 name = '%sProc' % (datatype)
573 573
574 574 if datatype == None:
575 575 datatype = name.replace('Proc', '')
576 576
577 577 self.id = str(id)
578 578 self.name = name
579 579 self.datatype = datatype
580 580 self.inputId = inputId
581 581 self.parentId = parentId
582 582
583 583 self.opConfObjList = []
584 584
585 585 self.addOperation(name='run', optype='self')
586 586
587 587 def removeOperations(self):
588 588
589 589 for obj in self.opConfObjList:
590 590 del obj
591 591
592 592 self.opConfObjList = []
593 593 self.addOperation(name='run')
594 594
595 595 def addParameter(self, **kwargs):
596 596 '''
597 597 Add parameters to 'run' operation
598 598 '''
599 599 opObj = self.opConfObjList[0]
600 600
601 601 opObj.addParameter(**kwargs)
602 602
603 603 return opObj
604 604
605 605 def addOperation(self, name, optype='self'):
606 606
607 607 id = self.__getNewId()
608 608 priority = self.__getPriority()
609 609
610 610 opConfObj = OperationConf()
611 611 opConfObj.setup(id, name=name, priority=priority, type=optype)
612 612
613 613 self.opConfObjList.append(opConfObj)
614 614
615 615 return opConfObj
616 616
617 617 def makeXml(self, projectElement):
618 618
619 619 procUnitElement = SubElement(projectElement, self.ELEMENTNAME)
620 620 procUnitElement.set('id', str(self.id))
621 621 procUnitElement.set('name', self.name)
622 622 procUnitElement.set('datatype', self.datatype)
623 623 procUnitElement.set('inputId', str(self.inputId))
624 624
625 625 for opConfObj in self.opConfObjList:
626 626 opConfObj.makeXml(procUnitElement)
627 627
628 628 def readXml(self, upElement):
629 629
630 630 self.id = upElement.get('id')
631 631 self.name = upElement.get('name')
632 632 self.datatype = upElement.get('datatype')
633 633 self.inputId = upElement.get('inputId')
634 634
635 635 if self.ELEMENTNAME == 'ReadUnit':
636 636 self.datatype = self.datatype.replace('Reader', '')
637 637
638 638 if self.ELEMENTNAME == 'ProcUnit':
639 639 self.datatype = self.datatype.replace('Proc', '')
640 640
641 641 if self.inputId == 'None':
642 642 self.inputId = '0'
643 643
644 644 self.opConfObjList = []
645 645
646 646 opElementList = upElement.iter(OperationConf().getElementName())
647 647
648 648 for opElement in opElementList:
649 649 opConfObj = OperationConf()
650 650 opConfObj.readXml(opElement)
651 651 self.opConfObjList.append(opConfObj)
652 652
653 653 def printattr(self):
654 654
655 655 print '%s[%s]: name = %s, datatype = %s, inputId = %s' % (self.ELEMENTNAME,
656 656 self.id,
657 657 self.name,
658 658 self.datatype,
659 659 self.inputId)
660 660
661 661 for opConfObj in self.opConfObjList:
662 662 opConfObj.printattr()
663 663
664 664 def getKwargs(self):
665 665
666 666 opObj = self.opConfObjList[0]
667 667 kwargs = opObj.getKwargs()
668 668
669 669 return kwargs
670 670
671 671 def createObjects(self, plotter_queue=None):
672 672
673 673 className = eval(self.name)
674 674 kwargs = self.getKwargs()
675 675 procUnitObj = className(**kwargs)
676 676
677 677 for opConfObj in self.opConfObjList:
678 678
679 679 if opConfObj.type == 'self' and self.name == 'run':
680 680 continue
681 681 elif opConfObj.type == 'self':
682 682 procUnitObj.addOperationKwargs(
683 683 opConfObj.id, **opConfObj.getKwargs())
684 684 continue
685 685
686 686 opObj = opConfObj.createObject(plotter_queue)
687 687
688 688 self.opObjDict[opConfObj.id] = opObj
689 689
690 690 procUnitObj.addOperation(opObj, opConfObj.id)
691 691
692 692 self.procUnitObj = procUnitObj
693 693
694 694 return procUnitObj
695 695
696 696 def run(self):
697 697
698 698 is_ok = False
699 699
700 700 for opConfObj in self.opConfObjList:
701 701
702 702 kwargs = {}
703 703 for parmConfObj in opConfObj.getParameterObjList():
704 704 if opConfObj.name == 'run' and parmConfObj.name == 'datatype':
705 705 continue
706 706
707 707 kwargs[parmConfObj.name] = parmConfObj.getValue()
708 708
709 709 sts = self.procUnitObj.call(opType=opConfObj.type,
710 710 opName=opConfObj.name,
711 711 opId=opConfObj.id)
712 712
713 713 is_ok = is_ok or sts
714 714
715 715 return is_ok
716 716
717 717 def close(self):
718 718
719 719 for opConfObj in self.opConfObjList:
720 720 if opConfObj.type == 'self':
721 721 continue
722 722
723 723 opObj = self.procUnitObj.getOperationObj(opConfObj.id)
724 724 opObj.close()
725 725
726 726 self.procUnitObj.close()
727 727
728 728 return
729 729
730 730
731 731 class ReadUnitConf(ProcUnitConf):
732 732
733 733 path = None
734 734 startDate = None
735 735 endDate = None
736 736 startTime = None
737 737 endTime = None
738 738
739 739 ELEMENTNAME = 'ReadUnit'
740 740
741 741 def __init__(self):
742 742
743 743 self.id = None
744 744 self.datatype = None
745 745 self.name = None
746 746 self.inputId = None
747 747
748 748 self.parentId = None
749 749
750 750 self.opConfObjList = []
751 751 self.opObjList = []
752 752
753 753 def getElementName(self):
754 754
755 755 return self.ELEMENTNAME
756 756
757 757 def setup(self, id, name, datatype, path='', startDate='', endDate='',
758 758 startTime='', endTime='', parentId=None, server=None, **kwargs):
759 759
760 760 # Compatible with old signal chain version
761 761 if datatype == None and name == None:
762 762 raise ValueError, 'datatype or name should be defined'
763 763 if name == None:
764 764 if 'Reader' in datatype:
765 765 name = datatype
766 766 datatype = name.replace('Reader','')
767 767 else:
768 768 name = '{}Reader'.format(datatype)
769 769 if datatype == None:
770 770 if 'Reader' in name:
771 771 datatype = name.replace('Reader','')
772 772 else:
773 773 datatype = name
774 774 name = '{}Reader'.format(name)
775 775
776 776 self.id = id
777 777 self.name = name
778 778 self.datatype = datatype
779 779 if path != '':
780 780 self.path = os.path.abspath(path)
781 781 self.startDate = startDate
782 782 self.endDate = endDate
783 783 self.startTime = startTime
784 784 self.endTime = endTime
785 785 self.inputId = '0'
786 786 self.parentId = parentId
787 787 self.server = server
788 788 self.addRunOperation(**kwargs)
789 789
790 790 def update(self, **kwargs):
791 791
792 792 if 'datatype' in kwargs:
793 793 datatype = kwargs.pop('datatype')
794 794 if 'Reader' in datatype:
795 795 self.name = datatype
796 796 else:
797 797 self.name = '%sReader' % (datatype)
798 798 self.datatype = self.name.replace('Reader', '')
799 799
800 800 attrs = ('path', 'startDate', 'endDate',
801 801 'startTime', 'endTime', 'parentId')
802 802
803 803 for attr in attrs:
804 804 if attr in kwargs:
805 805 setattr(self, attr, kwargs.pop(attr))
806 806
807 807 self.inputId = '0'
808 808 self.updateRunOperation(**kwargs)
809 809
810 810 def removeOperations(self):
811 811
812 812 for obj in self.opConfObjList:
813 813 del obj
814 814
815 815 self.opConfObjList = []
816 816
817 817 def addRunOperation(self, **kwargs):
818 818
819 819 opObj = self.addOperation(name='run', optype='self')
820 820
821 821 if self.server is None:
822 822 opObj.addParameter(
823 823 name='datatype', value=self.datatype, format='str')
824 824 opObj.addParameter(name='path', value=self.path, format='str')
825 825 opObj.addParameter(
826 826 name='startDate', value=self.startDate, format='date')
827 827 opObj.addParameter(
828 828 name='endDate', value=self.endDate, format='date')
829 829 opObj.addParameter(
830 830 name='startTime', value=self.startTime, format='time')
831 831 opObj.addParameter(
832 832 name='endTime', value=self.endTime, format='time')
833 833
834 834 for key, value in kwargs.items():
835 835 opObj.addParameter(name=key, value=value,
836 836 format=type(value).__name__)
837 837 else:
838 838 opObj.addParameter(name='server', value=self.server, format='str')
839 839
840 840 return opObj
841 841
842 842 def updateRunOperation(self, **kwargs):
843 843
844 844 opObj = self.getOperationObj(name='run')
845 845 opObj.removeParameters()
846 846
847 847 opObj.addParameter(name='datatype', value=self.datatype, format='str')
848 848 opObj.addParameter(name='path', value=self.path, format='str')
849 849 opObj.addParameter(
850 850 name='startDate', value=self.startDate, format='date')
851 851 opObj.addParameter(name='endDate', value=self.endDate, format='date')
852 852 opObj.addParameter(
853 853 name='startTime', value=self.startTime, format='time')
854 854 opObj.addParameter(name='endTime', value=self.endTime, format='time')
855 855
856 856 for key, value in kwargs.items():
857 857 opObj.addParameter(name=key, value=value,
858 858 format=type(value).__name__)
859 859
860 860 return opObj
861 861
862 862 def readXml(self, upElement):
863 863
864 864 self.id = upElement.get('id')
865 865 self.name = upElement.get('name')
866 866 self.datatype = upElement.get('datatype')
867 867 self.inputId = upElement.get('inputId')
868 868
869 869 if self.ELEMENTNAME == 'ReadUnit':
870 870 self.datatype = self.datatype.replace('Reader', '')
871 871
872 872 if self.inputId == 'None':
873 873 self.inputId = '0'
874 874
875 875 self.opConfObjList = []
876 876
877 877 opElementList = upElement.iter(OperationConf().getElementName())
878 878
879 879 for opElement in opElementList:
880 880 opConfObj = OperationConf()
881 881 opConfObj.readXml(opElement)
882 882 self.opConfObjList.append(opConfObj)
883 883
884 884 if opConfObj.name == 'run':
885 885 self.path = opConfObj.getParameterValue('path')
886 886 self.startDate = opConfObj.getParameterValue('startDate')
887 887 self.endDate = opConfObj.getParameterValue('endDate')
888 888 self.startTime = opConfObj.getParameterValue('startTime')
889 889 self.endTime = opConfObj.getParameterValue('endTime')
890 890
891 891
892 892 class Project(Process):
893 893
894 894 id = None
895 895 # name = None
896 896 description = None
897 897 filename = None
898 898
899 899 procUnitConfObjDict = None
900 900
901 901 ELEMENTNAME = 'Project'
902 902
903 903 plotterQueue = None
904 904
905 905 def __init__(self, plotter_queue=None):
906 906
907 907 Process.__init__(self)
908 908 self.id = None
909 909 self.description = None
910 910 self.email = None
911 911 self.alarm = [0]
912 912 self.plotterQueue = plotter_queue
913 913 self.procUnitConfObjDict = {}
914 914
915 915 def __getNewId(self):
916 916
917 917 idList = self.procUnitConfObjDict.keys()
918 918
919 919 id = int(self.id) * 10
920 920
921 921 while True:
922 922 id += 1
923 923
924 924 if str(id) in idList:
925 925 continue
926 926
927 927 break
928 928
929 929 return str(id)
930 930
931 931 def getElementName(self):
932 932
933 933 return self.ELEMENTNAME
934 934
935 935 def getId(self):
936 936
937 937 return self.id
938 938
939 939 def updateId(self, new_id):
940 940
941 941 self.id = str(new_id)
942 942
943 943 keyList = self.procUnitConfObjDict.keys()
944 944 keyList.sort()
945 945
946 946 n = 1
947 947 newProcUnitConfObjDict = {}
948 948
949 949 for procKey in keyList:
950 950
951 951 procUnitConfObj = self.procUnitConfObjDict[procKey]
952 952 idProcUnit = str(int(self.id) * 10 + n)
953 953 procUnitConfObj.updateId(idProcUnit, parentId=self.id)
954 954 newProcUnitConfObjDict[idProcUnit] = procUnitConfObj
955 955 n += 1
956 956
957 957 self.procUnitConfObjDict = newProcUnitConfObjDict
958 958
959 959 def setup(self, id, name='', description='', email=None, alarm=[0]):
960 960
961 961 print
962 962 print '*' * 60
963 963 print ' Starting SIGNAL CHAIN PROCESSING v%s ' % schainpy.__version__
964 964 print '*' * 60
965 965 print
966 966 self.id = str(id)
967 967 self.description = description
968 968 self.email = email
969 969 self.alarm = alarm
970 970
971 971 def update(self, **kwargs):
972 972
973 973 for key, value in kwargs:
974 974 setattr(self, key, value)
975 975
976 976 def clone(self):
977 977
978 978 p = Project()
979 979 p.procUnitConfObjDict = self.procUnitConfObjDict
980 980 return p
981 981
982 982 def addReadUnit(self, id=None, datatype=None, name=None, **kwargs):
983 983
984 984 if id is None:
985 985 idReadUnit = self.__getNewId()
986 986 else:
987 987 idReadUnit = str(id)
988 988
989 989 readUnitConfObj = ReadUnitConf()
990 990 readUnitConfObj.setup(idReadUnit, name, datatype,
991 991 parentId=self.id, **kwargs)
992 992
993 993 self.procUnitConfObjDict[readUnitConfObj.getId()] = readUnitConfObj
994 994
995 995 return readUnitConfObj
996 996
997 997 def addProcUnit(self, inputId='0', datatype=None, name=None):
998 998
999 999 idProcUnit = self.__getNewId()
1000 1000
1001 1001 procUnitConfObj = ProcUnitConf()
1002 1002 procUnitConfObj.setup(idProcUnit, name, datatype,
1003 1003 inputId, parentId=self.id)
1004 1004
1005 1005 self.procUnitConfObjDict[procUnitConfObj.getId()] = procUnitConfObj
1006 1006
1007 1007 return procUnitConfObj
1008 1008
1009 1009 def removeProcUnit(self, id):
1010 1010
1011 1011 if id in self.procUnitConfObjDict.keys():
1012 1012 self.procUnitConfObjDict.pop(id)
1013 1013
1014 1014 def getReadUnitId(self):
1015 1015
1016 1016 readUnitConfObj = self.getReadUnitObj()
1017 1017
1018 1018 return readUnitConfObj.id
1019 1019
1020 1020 def getReadUnitObj(self):
1021 1021
1022 1022 for obj in self.procUnitConfObjDict.values():
1023 1023 if obj.getElementName() == 'ReadUnit':
1024 1024 return obj
1025 1025
1026 1026 return None
1027 1027
1028 1028 def getProcUnitObj(self, id=None, name=None):
1029 1029
1030 1030 if id != None:
1031 1031 return self.procUnitConfObjDict[id]
1032 1032
1033 1033 if name != None:
1034 1034 return self.getProcUnitObjByName(name)
1035 1035
1036 1036 return None
1037 1037
1038 1038 def getProcUnitObjByName(self, name):
1039 1039
1040 1040 for obj in self.procUnitConfObjDict.values():
1041 1041 if obj.name == name:
1042 1042 return obj
1043 1043
1044 1044 return None
1045 1045
1046 1046 def procUnitItems(self):
1047 1047
1048 1048 return self.procUnitConfObjDict.items()
1049 1049
1050 1050 def makeXml(self):
1051 1051
1052 1052 projectElement = Element('Project')
1053 1053 projectElement.set('id', str(self.id))
1054 1054 projectElement.set('name', self.name)
1055 1055 projectElement.set('description', self.description)
1056 1056
1057 1057 for procUnitConfObj in self.procUnitConfObjDict.values():
1058 1058 procUnitConfObj.makeXml(projectElement)
1059 1059
1060 1060 self.projectElement = projectElement
1061 1061
1062 1062 def writeXml(self, filename=None):
1063 1063
1064 1064 if filename == None:
1065 1065 if self.filename:
1066 1066 filename = self.filename
1067 1067 else:
1068 1068 filename = 'schain.xml'
1069 1069
1070 1070 if not filename:
1071 1071 print 'filename has not been defined. Use setFilename(filename) for do it.'
1072 1072 return 0
1073 1073
1074 1074 abs_file = os.path.abspath(filename)
1075 1075
1076 1076 if not os.access(os.path.dirname(abs_file), os.W_OK):
1077 1077 print 'No write permission on %s' % os.path.dirname(abs_file)
1078 1078 return 0
1079 1079
1080 1080 if os.path.isfile(abs_file) and not(os.access(abs_file, os.W_OK)):
1081 1081 print 'File %s already exists and it could not be overwriten' % abs_file
1082 1082 return 0
1083 1083
1084 1084 self.makeXml()
1085 1085
1086 1086 ElementTree(self.projectElement).write(abs_file, method='xml')
1087 1087
1088 1088 self.filename = abs_file
1089 1089
1090 1090 return 1
1091 1091
1092 1092 def readXml(self, filename=None):
1093 1093
1094 1094 if not filename:
1095 1095 print 'filename is not defined'
1096 1096 return 0
1097 1097
1098 1098 abs_file = os.path.abspath(filename)
1099 1099
1100 1100 if not os.path.isfile(abs_file):
1101 1101 print '%s file does not exist' % abs_file
1102 1102 return 0
1103 1103
1104 1104 self.projectElement = None
1105 1105 self.procUnitConfObjDict = {}
1106 1106
1107 1107 try:
1108 1108 self.projectElement = ElementTree().parse(abs_file)
1109 1109 except:
1110 1110 print 'Error reading %s, verify file format' % filename
1111 1111 return 0
1112 1112
1113 1113 self.project = self.projectElement.tag
1114 1114
1115 1115 self.id = self.projectElement.get('id')
1116 1116 self.name = self.projectElement.get('name')
1117 1117 self.description = self.projectElement.get('description')
1118 1118
1119 1119 readUnitElementList = self.projectElement.iter(
1120 1120 ReadUnitConf().getElementName())
1121 1121
1122 1122 for readUnitElement in readUnitElementList:
1123 1123 readUnitConfObj = ReadUnitConf()
1124 1124 readUnitConfObj.readXml(readUnitElement)
1125 1125
1126 1126 if readUnitConfObj.parentId == None:
1127 1127 readUnitConfObj.parentId = self.id
1128 1128
1129 1129 self.procUnitConfObjDict[readUnitConfObj.getId()] = readUnitConfObj
1130 1130
1131 1131 procUnitElementList = self.projectElement.iter(
1132 1132 ProcUnitConf().getElementName())
1133 1133
1134 1134 for procUnitElement in procUnitElementList:
1135 1135 procUnitConfObj = ProcUnitConf()
1136 1136 procUnitConfObj.readXml(procUnitElement)
1137 1137
1138 1138 if procUnitConfObj.parentId == None:
1139 1139 procUnitConfObj.parentId = self.id
1140 1140
1141 1141 self.procUnitConfObjDict[procUnitConfObj.getId()] = procUnitConfObj
1142 1142
1143 1143 self.filename = abs_file
1144 1144
1145 1145 return 1
1146 1146
1147 1147 def printattr(self):
1148 1148
1149 1149 print 'Project[%s]: name = %s, description = %s' % (self.id,
1150 1150 self.name,
1151 1151 self.description)
1152 1152
1153 1153 for procUnitConfObj in self.procUnitConfObjDict.values():
1154 1154 procUnitConfObj.printattr()
1155 1155
1156 1156 def createObjects(self):
1157 1157
1158 1158 for procUnitConfObj in self.procUnitConfObjDict.values():
1159 1159 procUnitConfObj.createObjects(self.plotterQueue)
1160 1160
1161 1161 def __connect(self, objIN, thisObj):
1162 1162
1163 1163 thisObj.setInput(objIN.getOutputObj())
1164 1164
1165 1165 def connectObjects(self):
1166 1166
1167 1167 for thisPUConfObj in self.procUnitConfObjDict.values():
1168 1168
1169 1169 inputId = thisPUConfObj.getInputId()
1170 1170
1171 1171 if int(inputId) == 0:
1172 1172 continue
1173 1173
1174 1174 # Get input object
1175 1175 puConfINObj = self.procUnitConfObjDict[inputId]
1176 1176 puObjIN = puConfINObj.getProcUnitObj()
1177 1177
1178 1178 # Get current object
1179 1179 thisPUObj = thisPUConfObj.getProcUnitObj()
1180 1180
1181 1181 self.__connect(puObjIN, thisPUObj)
1182 1182
1183 def __handleError(self, procUnitConfObj):
1183 def __handleError(self, procUnitConfObj, modes=None):
1184 1184
1185 1185 import socket
1186 1186
1187 if modes is None:
1188 modes = self.alarm
1189
1187 1190 err = traceback.format_exception(sys.exc_info()[0],
1188 1191 sys.exc_info()[1],
1189 1192 sys.exc_info()[2])
1190 1193
1191 1194 log.error('{}'.format(err[-1]), procUnitConfObj.name)
1192 1195
1193 1196 message = ''.join(err)
1194 1197
1195 1198 sys.stderr.write(message)
1196 1199
1197 1200 subject = 'SChain v%s: Error running %s\n' % (
1198 1201 schainpy.__version__, procUnitConfObj.name)
1199 1202
1200 1203 subtitle = '%s: %s\n' % (
1201 1204 procUnitConfObj.getElementName(), procUnitConfObj.name)
1202 1205 subtitle += 'Hostname: %s\n' % socket.gethostbyname(
1203 1206 socket.gethostname())
1204 1207 subtitle += 'Working directory: %s\n' % os.path.abspath('./')
1205 1208 subtitle += 'Configuration file: %s\n' % self.filename
1206 1209 subtitle += 'Time: %s\n' % str(datetime.datetime.now())
1207 1210
1208 1211 readUnitConfObj = self.getReadUnitObj()
1209 1212 if readUnitConfObj:
1210 1213 subtitle += '\nInput parameters:\n'
1211 1214 subtitle += '[Data path = %s]\n' % readUnitConfObj.path
1212 1215 subtitle += '[Data type = %s]\n' % readUnitConfObj.datatype
1213 1216 subtitle += '[Start date = %s]\n' % readUnitConfObj.startDate
1214 1217 subtitle += '[End date = %s]\n' % readUnitConfObj.endDate
1215 1218 subtitle += '[Start time = %s]\n' % readUnitConfObj.startTime
1216 1219 subtitle += '[End time = %s]\n' % readUnitConfObj.endTime
1217 1220
1218 schainpy.admin.alarm(
1219 modes=self.alarm,
1221 a = Alarm(
1222 modes=modes,
1220 1223 email=self.email,
1221 1224 message=message,
1222 1225 subject=subject,
1223 1226 subtitle=subtitle,
1224 1227 filename=self.filename
1225 1228 )
1226 1229
1230 a.start()
1231
1227 1232 def isPaused(self):
1228 1233 return 0
1229 1234
1230 1235 def isStopped(self):
1231 1236 return 0
1232 1237
1233 1238 def runController(self):
1234 1239 '''
1235 1240 returns 0 when this process has been stopped, 1 otherwise
1236 1241 '''
1237 1242
1238 1243 if self.isPaused():
1239 1244 print 'Process suspended'
1240 1245
1241 1246 while True:
1242 1247 time.sleep(0.1)
1243 1248
1244 1249 if not self.isPaused():
1245 1250 break
1246 1251
1247 1252 if self.isStopped():
1248 1253 break
1249 1254
1250 1255 print 'Process reinitialized'
1251 1256
1252 1257 if self.isStopped():
1253 1258 print 'Process stopped'
1254 1259 return 0
1255 1260
1256 1261 return 1
1257 1262
1258 1263 def setFilename(self, filename):
1259 1264
1260 1265 self.filename = filename
1261 1266
1262 1267 def setPlotterQueue(self, plotter_queue):
1263 1268
1264 1269 raise NotImplementedError, 'Use schainpy.controller_api.ControllerThread instead Project class'
1265 1270
1266 1271 def getPlotterQueue(self):
1267 1272
1268 1273 raise NotImplementedError, 'Use schainpy.controller_api.ControllerThread instead Project class'
1269 1274
1270 1275 def useExternalPlotter(self):
1271 1276
1272 1277 raise NotImplementedError, 'Use schainpy.controller_api.ControllerThread instead Project class'
1273 1278
1274 1279 def run(self):
1275 1280
1276 1281 log.success('Starting {}'.format(self.name))
1277 1282 self.start_time = time.time()
1278 1283 self.createObjects()
1279 1284 self.connectObjects()
1280 1285
1281 1286 keyList = self.procUnitConfObjDict.keys()
1282 1287 keyList.sort()
1283 1288
1284 1289 while(True):
1285 1290
1286 1291 is_ok = False
1287 1292
1288 1293 for procKey in keyList:
1289 1294
1290 1295 procUnitConfObj = self.procUnitConfObjDict[procKey]
1291 1296
1292 1297 try:
1293 1298 sts = procUnitConfObj.run()
1294 1299 is_ok = is_ok or sts
1295 j
1300 except SchainWarning:
1301 self.__handleError(procUnitConfObj, modes=[2, 3])
1296 1302 except KeyboardInterrupt:
1297 1303 is_ok = False
1298 1304 break
1299 1305 except ValueError, e:
1300 1306 time.sleep(0.5)
1301 1307 self.__handleError(procUnitConfObj)
1302 1308 is_ok = False
1303 1309 break
1304 1310 except:
1305 1311 time.sleep(0.5)
1306 1312 self.__handleError(procUnitConfObj)
1307 1313 is_ok = False
1308 1314 break
1309 1315
1310 1316 # If every process unit finished so end process
1311 1317 if not(is_ok):
1312 1318 break
1313 1319
1314 1320 if not self.runController():
1315 1321 break
1316 1322
1317 1323 # Closing every process
1318 1324 for procKey in keyList:
1319 1325 procUnitConfObj = self.procUnitConfObjDict[procKey]
1320 1326 procUnitConfObj.close()
1321 1327
1322 1328 log.success('{} finished (time: {}s)'.format(
1323 1329 self.name,
1324 1330 time.time()-self.start_time))
@@ -1,972 +1,971
1 1
2 2 import os
3 3 import time
4 4 import glob
5 5 import datetime
6 6 from multiprocessing import Process
7 7
8 8 import zmq
9 9 import numpy
10 10 import matplotlib
11 11 import matplotlib.pyplot as plt
12 12 from mpl_toolkits.axes_grid1 import make_axes_locatable
13 13 from matplotlib.ticker import FuncFormatter, LinearLocator, MultipleLocator
14 14
15 15 from schainpy.model.proc.jroproc_base import Operation
16 16 from schainpy.utils import log
17 17
18 18 jet_values = matplotlib.pyplot.get_cmap('jet', 100)(numpy.arange(100))[10:90]
19 19 blu_values = matplotlib.pyplot.get_cmap(
20 20 'seismic_r', 20)(numpy.arange(20))[10:15]
21 21 ncmap = matplotlib.colors.LinearSegmentedColormap.from_list(
22 22 'jro', numpy.vstack((blu_values, jet_values)))
23 23 matplotlib.pyplot.register_cmap(cmap=ncmap)
24 24
25 25 CMAPS = [plt.get_cmap(s) for s in ('jro', 'jet', 'viridis', 'plasma', 'inferno', 'Greys', 'seismic', 'bwr', 'coolwarm')]
26 26
27 27
28 28 def figpause(interval):
29 29 backend = plt.rcParams['backend']
30 30 if backend in matplotlib.rcsetup.interactive_bk:
31 31 figManager = matplotlib._pylab_helpers.Gcf.get_active()
32 32 if figManager is not None:
33 33 canvas = figManager.canvas
34 34 if canvas.figure.stale:
35 35 canvas.draw()
36 36 canvas.start_event_loop(interval)
37 37 return
38 38
39 39 def popup(message):
40 fig = plt.figure(figsize=(12, 9), facecolor='r')
40 fig = plt.figure(figsize=(12, 8), facecolor='r')
41 41 fig.text(0.5, 0.5, message, ha='center', va='center', size='20', weight='heavy', color='w')
42 42 fig.show()
43 43 figpause(1000)
44 44
45 45
46
47 46 class PlotData(Operation, Process):
48 47 '''
49 48 Base class for Schain plotting operations
50 49 '''
51 50
52 51 CODE = 'Figure'
53 52 colormap = 'jro'
54 53 bgcolor = 'white'
55 54 CONFLATE = False
56 55 __missing = 1E30
57 56
58 57 __attrs__ = ['show', 'save', 'xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax',
59 58 'zlimits', 'xlabel', 'ylabel', 'xaxis','cb_label', 'title',
60 59 'colorbar', 'bgcolor', 'width', 'height', 'localtime', 'oneFigure',
61 60 'showprofile', 'decimation']
62 61
63 62 def __init__(self, **kwargs):
64 63
65 64 Operation.__init__(self, plot=True, **kwargs)
66 65 Process.__init__(self)
67 66
68 67 self.kwargs['code'] = self.CODE
69 68 self.mp = False
70 69 self.data = None
71 70 self.isConfig = False
72 71 self.figures = []
73 72 self.axes = []
74 73 self.cb_axes = []
75 74 self.localtime = kwargs.pop('localtime', True)
76 75 self.show = kwargs.get('show', True)
77 76 self.save = kwargs.get('save', False)
78 77 self.colormap = kwargs.get('colormap', self.colormap)
79 78 self.colormap_coh = kwargs.get('colormap_coh', 'jet')
80 79 self.colormap_phase = kwargs.get('colormap_phase', 'RdBu_r')
81 80 self.colormaps = kwargs.get('colormaps', None)
82 81 self.bgcolor = kwargs.get('bgcolor', self.bgcolor)
83 82 self.showprofile = kwargs.get('showprofile', False)
84 83 self.title = kwargs.get('wintitle', self.CODE.upper())
85 84 self.cb_label = kwargs.get('cb_label', None)
86 85 self.cb_labels = kwargs.get('cb_labels', None)
87 86 self.xaxis = kwargs.get('xaxis', 'frequency')
88 87 self.zmin = kwargs.get('zmin', None)
89 88 self.zmax = kwargs.get('zmax', None)
90 89 self.zlimits = kwargs.get('zlimits', None)
91 90 self.xmin = kwargs.get('xmin', None)
92 91 self.xmax = kwargs.get('xmax', None)
93 92 self.xrange = kwargs.get('xrange', 24)
94 93 self.ymin = kwargs.get('ymin', None)
95 94 self.ymax = kwargs.get('ymax', None)
96 95 self.xlabel = kwargs.get('xlabel', None)
97 96 self.decimation = kwargs.get('decimation', None)
98 97 self.showSNR = kwargs.get('showSNR', False)
99 98 self.oneFigure = kwargs.get('oneFigure', True)
100 99 self.width = kwargs.get('width', None)
101 100 self.height = kwargs.get('height', None)
102 101 self.colorbar = kwargs.get('colorbar', True)
103 102 self.factors = kwargs.get('factors', [1, 1, 1, 1, 1, 1, 1, 1])
104 103 self.titles = kwargs.get('titles', [])
105 104 self.polar = False
106 105
107 106 def __fmtTime(self, x, pos):
108 107 '''
109 108 '''
110 109
111 110 return '{}'.format(self.getDateTime(x).strftime('%H:%M'))
112 111
113 112 def __setup(self):
114 113 '''
115 114 Common setup for all figures, here figures and axes are created
116 115 '''
117 116
118 117 if self.CODE not in self.data:
119 118 raise ValueError(log.error('Missing data for {}'.format(self.CODE),
120 119 self.name))
121 120
122 121 self.setup()
123 122
124 123 self.time_label = 'LT' if self.localtime else 'UTC'
125 124 if self.data.localtime:
126 125 self.getDateTime = datetime.datetime.fromtimestamp
127 126 else:
128 127 self.getDateTime = datetime.datetime.utcfromtimestamp
129 128
130 129 if self.width is None:
131 130 self.width = 8
132 131
133 132 self.figures = []
134 133 self.axes = []
135 134 self.cb_axes = []
136 135 self.pf_axes = []
137 136 self.cmaps = []
138 137
139 138 size = '15%' if self.ncols == 1 else '30%'
140 139 pad = '4%' if self.ncols == 1 else '8%'
141 140
142 141 if self.oneFigure:
143 142 if self.height is None:
144 143 self.height = 1.4 * self.nrows + 1
145 144 fig = plt.figure(figsize=(self.width, self.height),
146 145 edgecolor='k',
147 146 facecolor='w')
148 147 self.figures.append(fig)
149 148 for n in range(self.nplots):
150 149 ax = fig.add_subplot(self.nrows, self.ncols,
151 150 n + 1, polar=self.polar)
152 151 ax.tick_params(labelsize=8)
153 152 ax.firsttime = True
154 153 ax.index = 0
155 154 ax.press = None
156 155 self.axes.append(ax)
157 156 if self.showprofile:
158 157 cax = self.__add_axes(ax, size=size, pad=pad)
159 158 cax.tick_params(labelsize=8)
160 159 self.pf_axes.append(cax)
161 160 else:
162 161 if self.height is None:
163 162 self.height = 3
164 163 for n in range(self.nplots):
165 164 fig = plt.figure(figsize=(self.width, self.height),
166 165 edgecolor='k',
167 166 facecolor='w')
168 167 ax = fig.add_subplot(1, 1, 1, polar=self.polar)
169 168 ax.tick_params(labelsize=8)
170 169 ax.firsttime = True
171 170 ax.index = 0
172 171 ax.press = None
173 172 self.figures.append(fig)
174 173 self.axes.append(ax)
175 174 if self.showprofile:
176 175 cax = self.__add_axes(ax, size=size, pad=pad)
177 176 cax.tick_params(labelsize=8)
178 177 self.pf_axes.append(cax)
179 178
180 179 for n in range(self.nrows):
181 180 if self.colormaps is not None:
182 181 cmap = plt.get_cmap(self.colormaps[n])
183 182 else:
184 183 cmap = plt.get_cmap(self.colormap)
185 184 cmap.set_bad(self.bgcolor, 1.)
186 185 self.cmaps.append(cmap)
187 186
188 187 for fig in self.figures:
189 188 fig.canvas.mpl_connect('key_press_event', self.OnKeyPress)
190 189 fig.canvas.mpl_connect('scroll_event', self.OnBtnScroll)
191 190 fig.canvas.mpl_connect('button_press_event', self.onBtnPress)
192 191 fig.canvas.mpl_connect('motion_notify_event', self.onMotion)
193 192 fig.canvas.mpl_connect('button_release_event', self.onBtnRelease)
194 193 if self.show:
195 194 fig.show()
196 195
197 196 def OnKeyPress(self, event):
198 197 '''
199 198 Event for pressing keys (up, down) change colormap
200 199 '''
201 200 ax = event.inaxes
202 201 if ax in self.axes:
203 202 if event.key == 'down':
204 203 ax.index += 1
205 204 elif event.key == 'up':
206 205 ax.index -= 1
207 206 if ax.index < 0:
208 207 ax.index = len(CMAPS) - 1
209 208 elif ax.index == len(CMAPS):
210 209 ax.index = 0
211 210 cmap = CMAPS[ax.index]
212 211 ax.cbar.set_cmap(cmap)
213 212 ax.cbar.draw_all()
214 213 ax.plt.set_cmap(cmap)
215 214 ax.cbar.patch.figure.canvas.draw()
216 215 self.colormap = cmap.name
217 216
218 217 def OnBtnScroll(self, event):
219 218 '''
220 219 Event for scrolling, scale figure
221 220 '''
222 221 cb_ax = event.inaxes
223 222 if cb_ax in [ax.cbar.ax for ax in self.axes if ax.cbar]:
224 223 ax = [ax for ax in self.axes if cb_ax == ax.cbar.ax][0]
225 224 pt = ax.cbar.ax.bbox.get_points()[:, 1]
226 225 nrm = ax.cbar.norm
227 226 vmin, vmax, p0, p1, pS = (
228 227 nrm.vmin, nrm.vmax, pt[0], pt[1], event.y)
229 228 scale = 2 if event.step == 1 else 0.5
230 229 point = vmin + (vmax - vmin) / (p1 - p0) * (pS - p0)
231 230 ax.cbar.norm.vmin = point - scale * (point - vmin)
232 231 ax.cbar.norm.vmax = point - scale * (point - vmax)
233 232 ax.plt.set_norm(ax.cbar.norm)
234 233 ax.cbar.draw_all()
235 234 ax.cbar.patch.figure.canvas.draw()
236 235
237 236 def onBtnPress(self, event):
238 237 '''
239 238 Event for mouse button press
240 239 '''
241 240 cb_ax = event.inaxes
242 241 if cb_ax is None:
243 242 return
244 243
245 244 if cb_ax in [ax.cbar.ax for ax in self.axes if ax.cbar]:
246 245 cb_ax.press = event.x, event.y
247 246 else:
248 247 cb_ax.press = None
249 248
250 249 def onMotion(self, event):
251 250 '''
252 251 Event for move inside colorbar
253 252 '''
254 253 cb_ax = event.inaxes
255 254 if cb_ax is None:
256 255 return
257 256 if cb_ax not in [ax.cbar.ax for ax in self.axes if ax.cbar]:
258 257 return
259 258 if cb_ax.press is None:
260 259 return
261 260
262 261 ax = [ax for ax in self.axes if cb_ax == ax.cbar.ax][0]
263 262 xprev, yprev = cb_ax.press
264 263 dx = event.x - xprev
265 264 dy = event.y - yprev
266 265 cb_ax.press = event.x, event.y
267 266 scale = ax.cbar.norm.vmax - ax.cbar.norm.vmin
268 267 perc = 0.03
269 268
270 269 if event.button == 1:
271 270 ax.cbar.norm.vmin -= (perc * scale) * numpy.sign(dy)
272 271 ax.cbar.norm.vmax -= (perc * scale) * numpy.sign(dy)
273 272 elif event.button == 3:
274 273 ax.cbar.norm.vmin -= (perc * scale) * numpy.sign(dy)
275 274 ax.cbar.norm.vmax += (perc * scale) * numpy.sign(dy)
276 275
277 276 ax.cbar.draw_all()
278 277 ax.plt.set_norm(ax.cbar.norm)
279 278 ax.cbar.patch.figure.canvas.draw()
280 279
281 280 def onBtnRelease(self, event):
282 281 '''
283 282 Event for mouse button release
284 283 '''
285 284 cb_ax = event.inaxes
286 285 if cb_ax is not None:
287 286 cb_ax.press = None
288 287
289 288 def __add_axes(self, ax, size='30%', pad='8%'):
290 289 '''
291 290 Add new axes to the given figure
292 291 '''
293 292 divider = make_axes_locatable(ax)
294 293 nax = divider.new_horizontal(size=size, pad=pad)
295 294 ax.figure.add_axes(nax)
296 295 return nax
297 296
298 297 self.setup()
299 298
300 299 def setup(self):
301 300 '''
302 301 This method should be implemented in the child class, the following
303 302 attributes should be set:
304 303
305 304 self.nrows: number of rows
306 305 self.ncols: number of cols
307 306 self.nplots: number of plots (channels or pairs)
308 307 self.ylabel: label for Y axes
309 308 self.titles: list of axes title
310 309
311 310 '''
312 311 raise(NotImplementedError, 'Implement this method in child class')
313 312
314 313 def fill_gaps(self, x_buffer, y_buffer, z_buffer):
315 314 '''
316 315 Create a masked array for missing data
317 316 '''
318 317 if x_buffer.shape[0] < 2:
319 318 return x_buffer, y_buffer, z_buffer
320 319
321 320 deltas = x_buffer[1:] - x_buffer[0:-1]
322 321 x_median = numpy.median(deltas)
323 322
324 323 index = numpy.where(deltas > 5 * x_median)
325 324
326 325 if len(index[0]) != 0:
327 326 z_buffer[::, index[0], ::] = self.__missing
328 327 z_buffer = numpy.ma.masked_inside(z_buffer,
329 328 0.99 * self.__missing,
330 329 1.01 * self.__missing)
331 330
332 331 return x_buffer, y_buffer, z_buffer
333 332
334 333 def decimate(self):
335 334
336 335 # dx = int(len(self.x)/self.__MAXNUMX) + 1
337 336 dy = int(len(self.y) / self.decimation) + 1
338 337
339 338 # x = self.x[::dx]
340 339 x = self.x
341 340 y = self.y[::dy]
342 341 z = self.z[::, ::, ::dy]
343 342
344 343 return x, y, z
345 344
346 345 def format(self):
347 346 '''
348 347 Set min and max values, labels, ticks and titles
349 348 '''
350 349
351 350 if self.xmin is None:
352 351 xmin = self.min_time
353 352 else:
354 353 if self.xaxis is 'time':
355 354 dt = self.getDateTime(self.min_time)
356 355 xmin = (dt.replace(hour=int(self.xmin), minute=0, second=0) -
357 356 datetime.datetime(1970, 1, 1)).total_seconds()
358 357 if self.data.localtime:
359 358 xmin += time.timezone
360 359 else:
361 360 xmin = self.xmin
362 361
363 362 if self.xmax is None:
364 363 xmax = xmin + self.xrange * 60 * 60
365 364 else:
366 365 if self.xaxis is 'time':
367 366 dt = self.getDateTime(self.max_time)
368 367 xmax = (dt.replace(hour=int(self.xmax), minute=59, second=59) -
369 368 datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=1)).total_seconds()
370 369 if self.data.localtime:
371 370 xmax += time.timezone
372 371 else:
373 372 xmax = self.xmax
374 373
375 374 ymin = self.ymin if self.ymin else numpy.nanmin(self.y)
376 375 ymax = self.ymax if self.ymax else numpy.nanmax(self.y)
377 376
378 377 Y = numpy.array([5, 10, 20, 50, 100, 200, 500, 1000, 2000])
379 378 i = 1 if numpy.where(ymax-ymin < Y)[0][0] < 0 else numpy.where(ymax-ymin < Y)[0][0]
380 379 ystep = Y[i] / 5
381 380
382 381 for n, ax in enumerate(self.axes):
383 382 if ax.firsttime:
384 383 ax.set_facecolor(self.bgcolor)
385 384 ax.yaxis.set_major_locator(MultipleLocator(ystep))
386 385 if self.xaxis is 'time':
387 386 ax.xaxis.set_major_formatter(FuncFormatter(self.__fmtTime))
388 387 ax.xaxis.set_major_locator(LinearLocator(9))
389 388 if self.xlabel is not None:
390 389 ax.set_xlabel(self.xlabel)
391 390 ax.set_ylabel(self.ylabel)
392 391 ax.firsttime = False
393 392 if self.showprofile:
394 393 self.pf_axes[n].set_ylim(ymin, ymax)
395 394 self.pf_axes[n].set_xlim(self.zmin, self.zmax)
396 395 self.pf_axes[n].set_xlabel('dB')
397 396 self.pf_axes[n].grid(b=True, axis='x')
398 397 [tick.set_visible(False)
399 398 for tick in self.pf_axes[n].get_yticklabels()]
400 399 if self.colorbar:
401 400 ax.cbar = plt.colorbar(
402 401 ax.plt, ax=ax, fraction=0.05, pad=0.02, aspect=10)
403 402 ax.cbar.ax.tick_params(labelsize=8)
404 403 ax.cbar.ax.press = None
405 404 if self.cb_label:
406 405 ax.cbar.set_label(self.cb_label, size=8)
407 406 elif self.cb_labels:
408 407 ax.cbar.set_label(self.cb_labels[n], size=8)
409 408 else:
410 409 ax.cbar = None
411 410
412 411 if not self.polar:
413 412 ax.set_xlim(xmin, xmax)
414 413 ax.set_ylim(ymin, ymax)
415 414 ax.set_title('{} - {} {}'.format(
416 415 self.titles[n],
417 416 self.getDateTime(self.max_time).strftime('%H:%M:%S'),
418 417 self.time_label),
419 418 size=8)
420 419 else:
421 420 ax.set_title('{}'.format(self.titles[n]), size=8)
422 421 ax.set_ylim(0, 90)
423 422 ax.set_yticks(numpy.arange(0, 90, 20))
424 423 ax.yaxis.labelpad = 40
425 424
426 425 def __plot(self):
427 426 '''
428 427 '''
429 428 log.success('Plotting', self.name)
430 429
431 430 try:
432 431 self.plot()
433 432 self.format()
434 433 except:
435 434 log.warning('{} Plot could not be updated... check data'.format(self.CODE), self.name)
436 435
437 436 for n, fig in enumerate(self.figures):
438 437 if self.nrows == 0 or self.nplots == 0:
439 438 log.warning('No data', self.name)
440 439 fig.text(0.5, 0.5, 'No Data', fontsize='large', ha='center')
441 440 fig.canvas.manager.set_window_title(self.CODE)
442 441 continue
443 442
444 443 fig.tight_layout()
445 444 fig.canvas.manager.set_window_title('{} - {}'.format(self.title,
446 445 self.getDateTime(self.max_time).strftime('%Y/%m/%d')))
447 446 fig.canvas.draw()
448 447
449 448 if self.save and self.data.ended:
450 449 channels = range(self.nrows)
451 450 if self.oneFigure:
452 451 label = ''
453 452 else:
454 453 label = '_{}'.format(channels[n])
455 454 figname = os.path.join(
456 455 self.save,
457 456 '{}{}_{}.png'.format(
458 457 self.CODE,
459 458 label,
460 459 self.getDateTime(self.saveTime).strftime(
461 460 '%Y%m%d_%H%M%S'),
462 461 )
463 462 )
464 463 log.log('Saving figure: {}'.format(figname), self.name)
465 464 fig.savefig(figname)
466 465
467 466 def plot(self):
468 467 '''
469 468 '''
470 469 raise(NotImplementedError, 'Implement this method in child class')
471 470
472 471 def run(self):
473 472
474 473 log.success('Starting', self.name)
475 474
476 475 context = zmq.Context()
477 476 receiver = context.socket(zmq.SUB)
478 477 receiver.setsockopt(zmq.SUBSCRIBE, '')
479 478 receiver.setsockopt(zmq.CONFLATE, self.CONFLATE)
480 479
481 480 if 'server' in self.kwargs['parent']:
482 481 receiver.connect(
483 482 'ipc:///tmp/{}.plots'.format(self.kwargs['parent']['server']))
484 483 else:
485 484 receiver.connect("ipc:///tmp/zmq.plots")
486 485
487 486 while True:
488 487 try:
489 488 self.data = receiver.recv_pyobj(flags=zmq.NOBLOCK)
490 489 if self.data.localtime and self.localtime:
491 490 self.times = self.data.times
492 491 elif self.data.localtime and not self.localtime:
493 492 self.times = self.data.times + time.timezone
494 493 elif not self.data.localtime and self.localtime:
495 494 self.times = self.data.times - time.timezone
496 495 else:
497 496 self.times = self.data.times
498 497
499 498 self.min_time = self.times[0]
500 499 self.max_time = self.times[-1]
501 500
502 501 if self.isConfig is False:
503 502 self.__setup()
504 503 self.isConfig = True
505 504
506 505 self.__plot()
507 506
508 507 except zmq.Again as e:
509 508 log.log('Waiting for data...')
510 509 if self.data:
511 510 figpause(self.data.throttle)
512 511 else:
513 512 time.sleep(2)
514 513
515 514 def close(self):
516 515 if self.data:
517 516 self.__plot()
518 517
519 518
520 519 class PlotSpectraData(PlotData):
521 520 '''
522 521 Plot for Spectra data
523 522 '''
524 523
525 524 CODE = 'spc'
526 525 colormap = 'jro'
527 526
528 527 def setup(self):
529 528 self.nplots = len(self.data.channels)
530 529 self.ncols = int(numpy.sqrt(self.nplots) + 0.9)
531 530 self.nrows = int((1.0 * self.nplots / self.ncols) + 0.9)
532 531 self.width = 3.4 * self.ncols
533 532 self.height = 3 * self.nrows
534 533 self.cb_label = 'dB'
535 534 if self.showprofile:
536 535 self.width += 0.8 * self.ncols
537 536
538 537 self.ylabel = 'Range [km]'
539 538
540 539 def plot(self):
541 540 if self.xaxis == "frequency":
542 541 x = self.data.xrange[0]
543 542 self.xlabel = "Frequency (kHz)"
544 543 elif self.xaxis == "time":
545 544 x = self.data.xrange[1]
546 545 self.xlabel = "Time (ms)"
547 546 else:
548 547 x = self.data.xrange[2]
549 548 self.xlabel = "Velocity (m/s)"
550 549
551 550 if self.CODE == 'spc_mean':
552 551 x = self.data.xrange[2]
553 552 self.xlabel = "Velocity (m/s)"
554 553
555 554 self.titles = []
556 555
557 556 y = self.data.heights
558 557 self.y = y
559 558 z = self.data['spc']
560 559
561 560 for n, ax in enumerate(self.axes):
562 561 noise = self.data['noise'][n][-1]
563 562 if self.CODE == 'spc_mean':
564 563 mean = self.data['mean'][n][-1]
565 564 if ax.firsttime:
566 565 self.xmax = self.xmax if self.xmax else numpy.nanmax(x)
567 566 self.xmin = self.xmin if self.xmin else -self.xmax
568 567 self.zmin = self.zmin if self.zmin else numpy.nanmin(z)
569 568 self.zmax = self.zmax if self.zmax else numpy.nanmax(z)
570 569 ax.plt = ax.pcolormesh(x, y, z[n].T,
571 570 vmin=self.zmin,
572 571 vmax=self.zmax,
573 572 cmap=plt.get_cmap(self.colormap)
574 573 )
575 574
576 575 if self.showprofile:
577 576 ax.plt_profile = self.pf_axes[n].plot(
578 577 self.data['rti'][n][-1], y)[0]
579 578 ax.plt_noise = self.pf_axes[n].plot(numpy.repeat(noise, len(y)), y,
580 579 color="k", linestyle="dashed", lw=1)[0]
581 580 if self.CODE == 'spc_mean':
582 581 ax.plt_mean = ax.plot(mean, y, color='k')[0]
583 582 else:
584 583 ax.plt.set_array(z[n].T.ravel())
585 584 if self.showprofile:
586 585 ax.plt_profile.set_data(self.data['rti'][n][-1], y)
587 586 ax.plt_noise.set_data(numpy.repeat(noise, len(y)), y)
588 587 if self.CODE == 'spc_mean':
589 588 ax.plt_mean.set_data(mean, y)
590 589
591 590 self.titles.append('CH {}: {:3.2f}dB'.format(n, noise))
592 591 self.saveTime = self.max_time
593 592
594 593
595 594 class PlotCrossSpectraData(PlotData):
596 595
597 596 CODE = 'cspc'
598 597 zmin_coh = None
599 598 zmax_coh = None
600 599 zmin_phase = None
601 600 zmax_phase = None
602 601
603 602 def setup(self):
604 603
605 604 self.ncols = 4
606 605 self.nrows = len(self.data.pairs)
607 606 self.nplots = self.nrows * 4
608 607 self.width = 3.4 * self.ncols
609 608 self.height = 3 * self.nrows
610 609 self.ylabel = 'Range [km]'
611 610 self.showprofile = False
612 611
613 612 def plot(self):
614 613
615 614 if self.xaxis == "frequency":
616 615 x = self.data.xrange[0]
617 616 self.xlabel = "Frequency (kHz)"
618 617 elif self.xaxis == "time":
619 618 x = self.data.xrange[1]
620 619 self.xlabel = "Time (ms)"
621 620 else:
622 621 x = self.data.xrange[2]
623 622 self.xlabel = "Velocity (m/s)"
624 623
625 624 self.titles = []
626 625
627 626 y = self.data.heights
628 627 self.y = y
629 628 spc = self.data['spc']
630 629 cspc = self.data['cspc']
631 630
632 631 for n in range(self.nrows):
633 632 noise = self.data['noise'][n][-1]
634 633 pair = self.data.pairs[n]
635 634 ax = self.axes[4 * n]
636 635 ax3 = self.axes[4 * n + 3]
637 636 if ax.firsttime:
638 637 self.xmax = self.xmax if self.xmax else numpy.nanmax(x)
639 638 self.xmin = self.xmin if self.xmin else -self.xmax
640 639 self.zmin = self.zmin if self.zmin else numpy.nanmin(spc)
641 640 self.zmax = self.zmax if self.zmax else numpy.nanmax(spc)
642 641 ax.plt = ax.pcolormesh(x, y, spc[pair[0]].T,
643 642 vmin=self.zmin,
644 643 vmax=self.zmax,
645 644 cmap=plt.get_cmap(self.colormap)
646 645 )
647 646 else:
648 647 ax.plt.set_array(spc[pair[0]].T.ravel())
649 648 self.titles.append('CH {}: {:3.2f}dB'.format(n, noise))
650 649
651 650 ax = self.axes[4 * n + 1]
652 651 if ax.firsttime:
653 652 ax.plt = ax.pcolormesh(x, y, spc[pair[1]].T,
654 653 vmin=self.zmin,
655 654 vmax=self.zmax,
656 655 cmap=plt.get_cmap(self.colormap)
657 656 )
658 657 else:
659 658 ax.plt.set_array(spc[pair[1]].T.ravel())
660 659 self.titles.append('CH {}: {:3.2f}dB'.format(n, noise))
661 660
662 661 out = cspc[n] / numpy.sqrt(spc[pair[0]] * spc[pair[1]])
663 662 coh = numpy.abs(out)
664 663 phase = numpy.arctan2(out.imag, out.real) * 180 / numpy.pi
665 664
666 665 ax = self.axes[4 * n + 2]
667 666 if ax.firsttime:
668 667 ax.plt = ax.pcolormesh(x, y, coh.T,
669 668 vmin=0,
670 669 vmax=1,
671 670 cmap=plt.get_cmap(self.colormap_coh)
672 671 )
673 672 else:
674 673 ax.plt.set_array(coh.T.ravel())
675 674 self.titles.append(
676 675 'Coherence Ch{} * Ch{}'.format(pair[0], pair[1]))
677 676
678 677 ax = self.axes[4 * n + 3]
679 678 if ax.firsttime:
680 679 ax.plt = ax.pcolormesh(x, y, phase.T,
681 680 vmin=-180,
682 681 vmax=180,
683 682 cmap=plt.get_cmap(self.colormap_phase)
684 683 )
685 684 else:
686 685 ax.plt.set_array(phase.T.ravel())
687 686 self.titles.append('Phase CH{} * CH{}'.format(pair[0], pair[1]))
688 687
689 688 self.saveTime = self.max_time
690 689
691 690
692 691 class PlotSpectraMeanData(PlotSpectraData):
693 692 '''
694 693 Plot for Spectra and Mean
695 694 '''
696 695 CODE = 'spc_mean'
697 696 colormap = 'jro'
698 697
699 698
700 699 class PlotRTIData(PlotData):
701 700 '''
702 701 Plot for RTI data
703 702 '''
704 703
705 704 CODE = 'rti'
706 705 colormap = 'jro'
707 706
708 707 def setup(self):
709 708 self.xaxis = 'time'
710 709 self.ncols = 1
711 710 self.nrows = len(self.data.channels)
712 711 self.nplots = len(self.data.channels)
713 712 self.ylabel = 'Range [km]'
714 713 self.cb_label = 'dB'
715 714 self.titles = ['{} Channel {}'.format(
716 715 self.CODE.upper(), x) for x in range(self.nrows)]
717 716
718 717 def plot(self):
719 718 self.x = self.times
720 719 self.y = self.data.heights
721 720 self.z = self.data[self.CODE]
722 721 self.z = numpy.ma.masked_invalid(self.z)
723 722
724 723 if self.decimation is None:
725 724 x, y, z = self.fill_gaps(self.x, self.y, self.z)
726 725 else:
727 726 x, y, z = self.fill_gaps(*self.decimate())
728 727
729 728 for n, ax in enumerate(self.axes):
730 729 self.zmin = self.zmin if self.zmin else numpy.min(self.z)
731 730 self.zmax = self.zmax if self.zmax else numpy.max(self.z)
732 731 if ax.firsttime:
733 732 ax.plt = ax.pcolormesh(x, y, z[n].T,
734 733 vmin=self.zmin,
735 734 vmax=self.zmax,
736 735 cmap=plt.get_cmap(self.colormap)
737 736 )
738 737 if self.showprofile:
739 738 ax.plot_profile = self.pf_axes[n].plot(
740 739 self.data['rti'][n][-1], self.y)[0]
741 740 ax.plot_noise = self.pf_axes[n].plot(numpy.repeat(self.data['noise'][n][-1], len(self.y)), self.y,
742 741 color="k", linestyle="dashed", lw=1)[0]
743 742 else:
744 743 ax.collections.remove(ax.collections[0])
745 744 ax.plt = ax.pcolormesh(x, y, z[n].T,
746 745 vmin=self.zmin,
747 746 vmax=self.zmax,
748 747 cmap=plt.get_cmap(self.colormap)
749 748 )
750 749 if self.showprofile:
751 750 ax.plot_profile.set_data(self.data['rti'][n][-1], self.y)
752 751 ax.plot_noise.set_data(numpy.repeat(
753 752 self.data['noise'][n][-1], len(self.y)), self.y)
754 753
755 754 self.saveTime = self.min_time
756 755
757 756
758 757 class PlotCOHData(PlotRTIData):
759 758 '''
760 759 Plot for Coherence data
761 760 '''
762 761
763 762 CODE = 'coh'
764 763
765 764 def setup(self):
766 765 self.xaxis = 'time'
767 766 self.ncols = 1
768 767 self.nrows = len(self.data.pairs)
769 768 self.nplots = len(self.data.pairs)
770 769 self.ylabel = 'Range [km]'
771 770 if self.CODE == 'coh':
772 771 self.cb_label = ''
773 772 self.titles = [
774 773 'Coherence Map Ch{} * Ch{}'.format(x[0], x[1]) for x in self.data.pairs]
775 774 else:
776 775 self.cb_label = 'Degrees'
777 776 self.titles = [
778 777 'Phase Map Ch{} * Ch{}'.format(x[0], x[1]) for x in self.data.pairs]
779 778
780 779
781 780 class PlotPHASEData(PlotCOHData):
782 781 '''
783 782 Plot for Phase map data
784 783 '''
785 784
786 785 CODE = 'phase'
787 786 colormap = 'seismic'
788 787
789 788
790 789 class PlotNoiseData(PlotData):
791 790 '''
792 791 Plot for noise
793 792 '''
794 793
795 794 CODE = 'noise'
796 795
797 796 def setup(self):
798 797 self.xaxis = 'time'
799 798 self.ncols = 1
800 799 self.nrows = 1
801 800 self.nplots = 1
802 801 self.ylabel = 'Intensity [dB]'
803 802 self.titles = ['Noise']
804 803 self.colorbar = False
805 804
806 805 def plot(self):
807 806
808 807 x = self.times
809 808 xmin = self.min_time
810 809 xmax = xmin + self.xrange * 60 * 60
811 810 Y = self.data[self.CODE]
812 811
813 812 if self.axes[0].firsttime:
814 813 for ch in self.data.channels:
815 814 y = Y[ch]
816 815 self.axes[0].plot(x, y, lw=1, label='Ch{}'.format(ch))
817 816 plt.legend()
818 817 else:
819 818 for ch in self.data.channels:
820 819 y = Y[ch]
821 820 self.axes[0].lines[ch].set_data(x, y)
822 821
823 822 self.ymin = numpy.nanmin(Y) - 5
824 823 self.ymax = numpy.nanmax(Y) + 5
825 824 self.saveTime = self.min_time
826 825
827 826
828 827 class PlotSNRData(PlotRTIData):
829 828 '''
830 829 Plot for SNR Data
831 830 '''
832 831
833 832 CODE = 'snr'
834 833 colormap = 'jet'
835 834
836 835
837 836 class PlotDOPData(PlotRTIData):
838 837 '''
839 838 Plot for DOPPLER Data
840 839 '''
841 840
842 841 CODE = 'dop'
843 842 colormap = 'jet'
844 843
845 844
846 845 class PlotSkyMapData(PlotData):
847 846 '''
848 847 Plot for meteors detection data
849 848 '''
850 849
851 850 CODE = 'param'
852 851
853 852 def setup(self):
854 853
855 854 self.ncols = 1
856 855 self.nrows = 1
857 856 self.width = 7.2
858 857 self.height = 7.2
859 858 self.nplots = 1
860 859 self.xlabel = 'Zonal Zenith Angle (deg)'
861 860 self.ylabel = 'Meridional Zenith Angle (deg)'
862 861 self.polar = True
863 862 self.ymin = -180
864 863 self.ymax = 180
865 864 self.colorbar = False
866 865
867 866 def plot(self):
868 867
869 868 arrayParameters = numpy.concatenate(self.data['param'])
870 869 error = arrayParameters[:, -1]
871 870 indValid = numpy.where(error == 0)[0]
872 871 finalMeteor = arrayParameters[indValid, :]
873 872 finalAzimuth = finalMeteor[:, 3]
874 873 finalZenith = finalMeteor[:, 4]
875 874
876 875 x = finalAzimuth * numpy.pi / 180
877 876 y = finalZenith
878 877
879 878 ax = self.axes[0]
880 879
881 880 if ax.firsttime:
882 881 ax.plot = ax.plot(x, y, 'bo', markersize=5)[0]
883 882 else:
884 883 ax.plot.set_data(x, y)
885 884
886 885 dt1 = self.getDateTime(self.min_time).strftime('%y/%m/%d %H:%M:%S')
887 886 dt2 = self.getDateTime(self.max_time).strftime('%y/%m/%d %H:%M:%S')
888 887 title = 'Meteor Detection Sky Map\n %s - %s \n Number of events: %5.0f\n' % (dt1,
889 888 dt2,
890 889 len(x))
891 890 self.titles[0] = title
892 891 self.saveTime = self.max_time
893 892
894 893
895 894 class PlotParamData(PlotRTIData):
896 895 '''
897 896 Plot for data_param object
898 897 '''
899 898
900 899 CODE = 'param'
901 900 colormap = 'seismic'
902 901
903 902 def setup(self):
904 903 self.xaxis = 'time'
905 904 self.ncols = 1
906 905 self.nrows = self.data.shape(self.CODE)[0]
907 906 self.nplots = self.nrows
908 907 if self.showSNR:
909 908 self.nrows += 1
910 909 self.nplots += 1
911 910
912 911 self.ylabel = 'Height [km]'
913 912 if not self.titles:
914 913 self.titles = self.data.parameters \
915 914 if self.data.parameters else ['Param {}'.format(x) for x in xrange(self.nrows)]
916 915 if self.showSNR:
917 916 self.titles.append('SNR')
918 917
919 918 def plot(self):
920 919 self.data.normalize_heights()
921 920 self.x = self.times
922 921 self.y = self.data.heights
923 922 if self.showSNR:
924 923 self.z = numpy.concatenate(
925 924 (self.data[self.CODE], self.data['snr'])
926 925 )
927 926 else:
928 927 self.z = self.data[self.CODE]
929 928
930 929 self.z = numpy.ma.masked_invalid(self.z)
931 930
932 931 if self.decimation is None:
933 932 x, y, z = self.fill_gaps(self.x, self.y, self.z)
934 933 else:
935 934 x, y, z = self.fill_gaps(*self.decimate())
936 935
937 936 for n, ax in enumerate(self.axes):
938 937
939 938 self.zmax = self.zmax if self.zmax is not None else numpy.max(
940 939 self.z[n])
941 940 self.zmin = self.zmin if self.zmin is not None else numpy.min(
942 941 self.z[n])
943 942
944 943 if ax.firsttime:
945 944 if self.zlimits is not None:
946 945 self.zmin, self.zmax = self.zlimits[n]
947 946
948 947 ax.plt = ax.pcolormesh(x, y, z[n].T * self.factors[n],
949 948 vmin=self.zmin,
950 949 vmax=self.zmax,
951 950 cmap=self.cmaps[n]
952 951 )
953 952 else:
954 953 if self.zlimits is not None:
955 954 self.zmin, self.zmax = self.zlimits[n]
956 955 ax.collections.remove(ax.collections[0])
957 956 ax.plt = ax.pcolormesh(x, y, z[n].T * self.factors[n],
958 957 vmin=self.zmin,
959 958 vmax=self.zmax,
960 959 cmap=self.cmaps[n]
961 960 )
962 961
963 962 self.saveTime = self.min_time
964 963
965 964
966 965 class PlotOutputData(PlotParamData):
967 966 '''
968 967 Plot data_output object
969 968 '''
970 969
971 970 CODE = 'output'
972 971 colormap = 'seismic'
General Comments 0
You need to be logged in to leave comments. Login now