##// END OF EJS Templates
Fix Spectra shift fft & xticks in new spectra plot
Juan C. Espinoza -
r1170:05569dc01e89 master
parent child
Show More
@@ -1,1148 +1,1154
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 matplotlib.patches import Polygon
13 13 from mpl_toolkits.axes_grid1 import make_axes_locatable
14 14 from matplotlib.ticker import FuncFormatter, LinearLocator, MultipleLocator
15 15
16 16 from schainpy.model.proc.jroproc_base import Operation
17 17 from schainpy.utils import log
18 18
19 19 jet_values = matplotlib.pyplot.get_cmap('jet', 100)(numpy.arange(100))[10:90]
20 20 blu_values = matplotlib.pyplot.get_cmap(
21 21 'seismic_r', 20)(numpy.arange(20))[10:15]
22 22 ncmap = matplotlib.colors.LinearSegmentedColormap.from_list(
23 23 'jro', numpy.vstack((blu_values, jet_values)))
24 24 matplotlib.pyplot.register_cmap(cmap=ncmap)
25 25
26 26 CMAPS = [plt.get_cmap(s) for s in ('jro', 'jet', 'viridis', 'plasma', 'inferno', 'Greys', 'seismic', 'bwr', 'coolwarm')]
27 27
28 28 EARTH_RADIUS = 6.3710e3
29 29
30 30 def ll2xy(lat1, lon1, lat2, lon2):
31 31
32 32 p = 0.017453292519943295
33 33 a = 0.5 - numpy.cos((lat2 - lat1) * p)/2 + numpy.cos(lat1 * p) * numpy.cos(lat2 * p) * (1 - numpy.cos((lon2 - lon1) * p)) / 2
34 34 r = 12742 * numpy.arcsin(numpy.sqrt(a))
35 35 theta = numpy.arctan2(numpy.sin((lon2-lon1)*p)*numpy.cos(lat2*p), numpy.cos(lat1*p)*numpy.sin(lat2*p)-numpy.sin(lat1*p)*numpy.cos(lat2*p)*numpy.cos((lon2-lon1)*p))
36 36 theta = -theta + numpy.pi/2
37 37 return r*numpy.cos(theta), r*numpy.sin(theta)
38 38
39 39 def km2deg(km):
40 40 '''
41 41 Convert distance in km to degrees
42 42 '''
43 43
44 44 return numpy.rad2deg(km/EARTH_RADIUS)
45 45
46 46 def figpause(interval):
47 47 backend = plt.rcParams['backend']
48 48 if backend in matplotlib.rcsetup.interactive_bk:
49 49 figManager = matplotlib._pylab_helpers.Gcf.get_active()
50 50 if figManager is not None:
51 51 canvas = figManager.canvas
52 52 if canvas.figure.stale:
53 53 canvas.draw()
54 54 try:
55 55 canvas.start_event_loop(interval)
56 56 except:
57 57 pass
58 58 return
59 59
60 60 def popup(message):
61 61 '''
62 62 '''
63 63
64 64 fig = plt.figure(figsize=(12, 8), facecolor='r')
65 65 text = '\n'.join([s.strip() for s in message.split(':')])
66 66 fig.text(0.01, 0.5, text, ha='left', va='center', size='20', weight='heavy', color='w')
67 67 fig.show()
68 68 figpause(1000)
69 69
70 70
71 71 class PlotData(Operation, Process):
72 72 '''
73 73 Base class for Schain plotting operations
74 74 '''
75 75
76 76 CODE = 'Figure'
77 77 colormap = 'jro'
78 78 bgcolor = 'white'
79 79 CONFLATE = False
80 80 __missing = 1E30
81 81
82 82 __attrs__ = ['show', 'save', 'xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax',
83 83 'zlimits', 'xlabel', 'ylabel', 'xaxis','cb_label', 'title',
84 84 'colorbar', 'bgcolor', 'width', 'height', 'localtime', 'oneFigure',
85 85 'showprofile', 'decimation', 'ftp']
86 86
87 87 def __init__(self, **kwargs):
88 88
89 89 Operation.__init__(self, plot=True, **kwargs)
90 90 Process.__init__(self)
91 91
92 92 self.kwargs['code'] = self.CODE
93 93 self.mp = False
94 94 self.data = None
95 95 self.isConfig = False
96 96 self.figures = []
97 97 self.axes = []
98 98 self.cb_axes = []
99 99 self.localtime = kwargs.pop('localtime', True)
100 100 self.show = kwargs.get('show', True)
101 101 self.save = kwargs.get('save', False)
102 102 self.ftp = kwargs.get('ftp', False)
103 103 self.colormap = kwargs.get('colormap', self.colormap)
104 104 self.colormap_coh = kwargs.get('colormap_coh', 'jet')
105 105 self.colormap_phase = kwargs.get('colormap_phase', 'RdBu_r')
106 106 self.colormaps = kwargs.get('colormaps', None)
107 107 self.bgcolor = kwargs.get('bgcolor', self.bgcolor)
108 108 self.showprofile = kwargs.get('showprofile', False)
109 109 self.title = kwargs.get('wintitle', self.CODE.upper())
110 110 self.cb_label = kwargs.get('cb_label', None)
111 111 self.cb_labels = kwargs.get('cb_labels', None)
112 112 self.labels = kwargs.get('labels', None)
113 113 self.xaxis = kwargs.get('xaxis', 'frequency')
114 114 self.zmin = kwargs.get('zmin', None)
115 115 self.zmax = kwargs.get('zmax', None)
116 116 self.zlimits = kwargs.get('zlimits', None)
117 117 self.xmin = kwargs.get('xmin', None)
118 118 self.xmax = kwargs.get('xmax', None)
119 119 self.xrange = kwargs.get('xrange', 24)
120 120 self.xscale = kwargs.get('xscale', None)
121 121 self.ymin = kwargs.get('ymin', None)
122 122 self.ymax = kwargs.get('ymax', None)
123 123 self.yscale = kwargs.get('yscale', None)
124 124 self.xlabel = kwargs.get('xlabel', None)
125 125 self.decimation = kwargs.get('decimation', None)
126 126 self.showSNR = kwargs.get('showSNR', False)
127 127 self.oneFigure = kwargs.get('oneFigure', True)
128 128 self.width = kwargs.get('width', None)
129 129 self.height = kwargs.get('height', None)
130 130 self.colorbar = kwargs.get('colorbar', True)
131 131 self.factors = kwargs.get('factors', [1, 1, 1, 1, 1, 1, 1, 1])
132 132 self.channels = kwargs.get('channels', None)
133 133 self.titles = kwargs.get('titles', [])
134 134 self.polar = False
135 135 self.grid = kwargs.get('grid', False)
136 136
137 137 def __fmtTime(self, x, pos):
138 138 '''
139 139 '''
140 140
141 141 return '{}'.format(self.getDateTime(x).strftime('%H:%M'))
142 142
143 143 def __setup(self):
144 144 '''
145 145 Common setup for all figures, here figures and axes are created
146 146 '''
147 147
148 148 if self.CODE not in self.data:
149 149 raise ValueError(log.error('Missing data for {}'.format(self.CODE),
150 150 self.name))
151 151
152 152 self.setup()
153 153
154 154 self.time_label = 'LT' if self.localtime else 'UTC'
155 155 if self.data.localtime:
156 156 self.getDateTime = datetime.datetime.fromtimestamp
157 157 else:
158 158 self.getDateTime = datetime.datetime.utcfromtimestamp
159 159
160 160 if self.width is None:
161 161 self.width = 8
162 162
163 163 self.figures = []
164 164 self.axes = []
165 165 self.cb_axes = []
166 166 self.pf_axes = []
167 167 self.cmaps = []
168 168
169 169 size = '15%' if self.ncols == 1 else '30%'
170 170 pad = '4%' if self.ncols == 1 else '8%'
171 171
172 172 if self.oneFigure:
173 173 if self.height is None:
174 174 self.height = 1.4 * self.nrows + 1
175 175 fig = plt.figure(figsize=(self.width, self.height),
176 176 edgecolor='k',
177 177 facecolor='w')
178 178 self.figures.append(fig)
179 179 for n in range(self.nplots):
180 180 ax = fig.add_subplot(self.nrows, self.ncols,
181 181 n + 1, polar=self.polar)
182 182 ax.tick_params(labelsize=8)
183 183 ax.firsttime = True
184 184 ax.index = 0
185 185 ax.press = None
186 186 self.axes.append(ax)
187 187 if self.showprofile:
188 188 cax = self.__add_axes(ax, size=size, pad=pad)
189 189 cax.tick_params(labelsize=8)
190 190 self.pf_axes.append(cax)
191 191 else:
192 192 if self.height is None:
193 193 self.height = 3
194 194 for n in range(self.nplots):
195 195 fig = plt.figure(figsize=(self.width, self.height),
196 196 edgecolor='k',
197 197 facecolor='w')
198 198 ax = fig.add_subplot(1, 1, 1, polar=self.polar)
199 199 ax.tick_params(labelsize=8)
200 200 ax.firsttime = True
201 201 ax.index = 0
202 202 ax.press = None
203 203 self.figures.append(fig)
204 204 self.axes.append(ax)
205 205 if self.showprofile:
206 206 cax = self.__add_axes(ax, size=size, pad=pad)
207 207 cax.tick_params(labelsize=8)
208 208 self.pf_axes.append(cax)
209 209
210 210 for n in range(self.nrows):
211 211 if self.colormaps is not None:
212 212 cmap = plt.get_cmap(self.colormaps[n])
213 213 else:
214 214 cmap = plt.get_cmap(self.colormap)
215 215 cmap.set_bad(self.bgcolor, 1.)
216 216 self.cmaps.append(cmap)
217 217
218 218 for fig in self.figures:
219 219 fig.canvas.mpl_connect('key_press_event', self.OnKeyPress)
220 220 fig.canvas.mpl_connect('scroll_event', self.OnBtnScroll)
221 221 fig.canvas.mpl_connect('button_press_event', self.onBtnPress)
222 222 fig.canvas.mpl_connect('motion_notify_event', self.onMotion)
223 223 fig.canvas.mpl_connect('button_release_event', self.onBtnRelease)
224 224 if self.show:
225 225 fig.show()
226 226
227 227 def OnKeyPress(self, event):
228 228 '''
229 229 Event for pressing keys (up, down) change colormap
230 230 '''
231 231 ax = event.inaxes
232 232 if ax in self.axes:
233 233 if event.key == 'down':
234 234 ax.index += 1
235 235 elif event.key == 'up':
236 236 ax.index -= 1
237 237 if ax.index < 0:
238 238 ax.index = len(CMAPS) - 1
239 239 elif ax.index == len(CMAPS):
240 240 ax.index = 0
241 241 cmap = CMAPS[ax.index]
242 242 ax.cbar.set_cmap(cmap)
243 243 ax.cbar.draw_all()
244 244 ax.plt.set_cmap(cmap)
245 245 ax.cbar.patch.figure.canvas.draw()
246 246 self.colormap = cmap.name
247 247
248 248 def OnBtnScroll(self, event):
249 249 '''
250 250 Event for scrolling, scale figure
251 251 '''
252 252 cb_ax = event.inaxes
253 253 if cb_ax in [ax.cbar.ax for ax in self.axes if ax.cbar]:
254 254 ax = [ax for ax in self.axes if cb_ax == ax.cbar.ax][0]
255 255 pt = ax.cbar.ax.bbox.get_points()[:, 1]
256 256 nrm = ax.cbar.norm
257 257 vmin, vmax, p0, p1, pS = (
258 258 nrm.vmin, nrm.vmax, pt[0], pt[1], event.y)
259 259 scale = 2 if event.step == 1 else 0.5
260 260 point = vmin + (vmax - vmin) / (p1 - p0) * (pS - p0)
261 261 ax.cbar.norm.vmin = point - scale * (point - vmin)
262 262 ax.cbar.norm.vmax = point - scale * (point - vmax)
263 263 ax.plt.set_norm(ax.cbar.norm)
264 264 ax.cbar.draw_all()
265 265 ax.cbar.patch.figure.canvas.draw()
266 266
267 267 def onBtnPress(self, event):
268 268 '''
269 269 Event for mouse button press
270 270 '''
271 271 cb_ax = event.inaxes
272 272 if cb_ax is None:
273 273 return
274 274
275 275 if cb_ax in [ax.cbar.ax for ax in self.axes if ax.cbar]:
276 276 cb_ax.press = event.x, event.y
277 277 else:
278 278 cb_ax.press = None
279 279
280 280 def onMotion(self, event):
281 281 '''
282 282 Event for move inside colorbar
283 283 '''
284 284 cb_ax = event.inaxes
285 285 if cb_ax is None:
286 286 return
287 287 if cb_ax not in [ax.cbar.ax for ax in self.axes if ax.cbar]:
288 288 return
289 289 if cb_ax.press is None:
290 290 return
291 291
292 292 ax = [ax for ax in self.axes if cb_ax == ax.cbar.ax][0]
293 293 xprev, yprev = cb_ax.press
294 294 dx = event.x - xprev
295 295 dy = event.y - yprev
296 296 cb_ax.press = event.x, event.y
297 297 scale = ax.cbar.norm.vmax - ax.cbar.norm.vmin
298 298 perc = 0.03
299 299
300 300 if event.button == 1:
301 301 ax.cbar.norm.vmin -= (perc * scale) * numpy.sign(dy)
302 302 ax.cbar.norm.vmax -= (perc * scale) * numpy.sign(dy)
303 303 elif event.button == 3:
304 304 ax.cbar.norm.vmin -= (perc * scale) * numpy.sign(dy)
305 305 ax.cbar.norm.vmax += (perc * scale) * numpy.sign(dy)
306 306
307 307 ax.cbar.draw_all()
308 308 ax.plt.set_norm(ax.cbar.norm)
309 309 ax.cbar.patch.figure.canvas.draw()
310 310
311 311 def onBtnRelease(self, event):
312 312 '''
313 313 Event for mouse button release
314 314 '''
315 315 cb_ax = event.inaxes
316 316 if cb_ax is not None:
317 317 cb_ax.press = None
318 318
319 319 def __add_axes(self, ax, size='30%', pad='8%'):
320 320 '''
321 321 Add new axes to the given figure
322 322 '''
323 323 divider = make_axes_locatable(ax)
324 324 nax = divider.new_horizontal(size=size, pad=pad)
325 325 ax.figure.add_axes(nax)
326 326 return nax
327 327
328 328 self.setup()
329 329
330 330 def setup(self):
331 331 '''
332 332 This method should be implemented in the child class, the following
333 333 attributes should be set:
334 334
335 335 self.nrows: number of rows
336 336 self.ncols: number of cols
337 337 self.nplots: number of plots (channels or pairs)
338 338 self.ylabel: label for Y axes
339 339 self.titles: list of axes title
340 340
341 341 '''
342 342 raise(NotImplementedError, 'Implement this method in child class')
343 343
344 344 def fill_gaps(self, x_buffer, y_buffer, z_buffer):
345 345 '''
346 346 Create a masked array for missing data
347 347 '''
348 348 if x_buffer.shape[0] < 2:
349 349 return x_buffer, y_buffer, z_buffer
350 350
351 351 deltas = x_buffer[1:] - x_buffer[0:-1]
352 352 x_median = numpy.median(deltas)
353 353
354 354 index = numpy.where(deltas > 5 * x_median)
355 355
356 356 if len(index[0]) != 0:
357 357 z_buffer[::, index[0], ::] = self.__missing
358 358 z_buffer = numpy.ma.masked_inside(z_buffer,
359 359 0.99 * self.__missing,
360 360 1.01 * self.__missing)
361 361
362 362 return x_buffer, y_buffer, z_buffer
363 363
364 364 def decimate(self):
365 365
366 366 # dx = int(len(self.x)/self.__MAXNUMX) + 1
367 367 dy = int(len(self.y) / self.decimation) + 1
368 368
369 369 # x = self.x[::dx]
370 370 x = self.x
371 371 y = self.y[::dy]
372 372 z = self.z[::, ::, ::dy]
373 373
374 374 return x, y, z
375 375
376 376 def format(self):
377 377 '''
378 378 Set min and max values, labels, ticks and titles
379 379 '''
380 380
381 381 if self.xmin is None:
382 382 xmin = self.min_time
383 383 else:
384 384 if self.xaxis is 'time':
385 385 dt = self.getDateTime(self.min_time)
386 386 xmin = (dt.replace(hour=int(self.xmin), minute=0, second=0) -
387 387 datetime.datetime(1970, 1, 1)).total_seconds()
388 388 if self.data.localtime:
389 389 xmin += time.timezone
390 390 else:
391 391 xmin = self.xmin
392 392
393 393 if self.xmax is None:
394 394 xmax = xmin + self.xrange * 60 * 60
395 395 else:
396 396 if self.xaxis is 'time':
397 397 dt = self.getDateTime(self.max_time)
398 398 xmax = (dt.replace(hour=int(self.xmax), minute=59, second=59) -
399 399 datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=1)).total_seconds()
400 400 if self.data.localtime:
401 401 xmax += time.timezone
402 402 else:
403 403 xmax = self.xmax
404 404
405 405 ymin = self.ymin if self.ymin else numpy.nanmin(self.y)
406 406 ymax = self.ymax if self.ymax else numpy.nanmax(self.y)
407 407
408 408 Y = numpy.array([1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000])
409 409 i = 1 if numpy.where(abs(ymax-ymin) <= Y)[0][0] < 0 else numpy.where(abs(ymax-ymin) <= Y)[0][0]
410 410 ystep = Y[i] / 10.
411 411
412 if self.xaxis is not 'time':
413 X = numpy.array([1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000])/2.
414 i = 1 if numpy.where(abs(xmax-xmin) <= X)[0][0] < 0 else numpy.where(abs(xmax-xmin) <= X)[0][0]
415 xstep = X[i] / 10.
416
412 417 for n, ax in enumerate(self.axes):
413 418 if ax.firsttime:
414 419 ax.set_facecolor(self.bgcolor)
415 420 ax.yaxis.set_major_locator(MultipleLocator(ystep))
416 ax.xaxis.set_major_locator(MultipleLocator(ystep))
417 421 if self.xscale:
418 422 ax.xaxis.set_major_formatter(FuncFormatter(lambda x, pos: '{0:g}'.format(x*self.xscale)))
419 423 if self.xscale:
420 424 ax.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: '{0:g}'.format(x*self.yscale)))
421 425 if self.xaxis is 'time':
422 426 ax.xaxis.set_major_formatter(FuncFormatter(self.__fmtTime))
423 427 ax.xaxis.set_major_locator(LinearLocator(9))
428 else:
429 ax.xaxis.set_major_locator(MultipleLocator(xstep))
424 430 if self.xlabel is not None:
425 431 ax.set_xlabel(self.xlabel)
426 432 ax.set_ylabel(self.ylabel)
427 433 ax.firsttime = False
428 434 if self.showprofile:
429 435 self.pf_axes[n].set_ylim(ymin, ymax)
430 436 self.pf_axes[n].set_xlim(self.zmin, self.zmax)
431 437 self.pf_axes[n].set_xlabel('dB')
432 438 self.pf_axes[n].grid(b=True, axis='x')
433 439 [tick.set_visible(False)
434 440 for tick in self.pf_axes[n].get_yticklabels()]
435 441 if self.colorbar:
436 442 ax.cbar = plt.colorbar(
437 443 ax.plt, ax=ax, fraction=0.05, pad=0.02, aspect=10)
438 444 ax.cbar.ax.tick_params(labelsize=8)
439 445 ax.cbar.ax.press = None
440 446 if self.cb_label:
441 447 ax.cbar.set_label(self.cb_label, size=8)
442 448 elif self.cb_labels:
443 449 ax.cbar.set_label(self.cb_labels[n], size=8)
444 450 else:
445 451 ax.cbar = None
446 452 if self.grid:
447 453 ax.grid(True)
448 454
449 455 if not self.polar:
450 456 ax.set_xlim(xmin, xmax)
451 457 ax.set_ylim(ymin, ymax)
452 458 ax.set_title('{} {} {}'.format(
453 459 self.titles[n],
454 460 self.getDateTime(self.max_time).strftime('%Y-%m-%dT%H:%M:%S'),
455 461 self.time_label),
456 462 size=8)
457 463 else:
458 464 ax.set_title('{}'.format(self.titles[n]), size=8)
459 465 ax.set_ylim(0, 90)
460 466 ax.set_yticks(numpy.arange(0, 90, 20))
461 467 ax.yaxis.labelpad = 40
462 468
463 469 def __plot(self):
464 470 '''
465 471 '''
466 472 log.log('Plotting', self.name)
467 473
468 474 try:
469 475 self.plot()
470 476 self.format()
471 477 except Exception as e:
472 478 log.warning('{} Plot could not be updated... check data'.format(self.CODE), self.name)
473 479 log.error(str(e), '')
474 480 return
475 481
476 482 for n, fig in enumerate(self.figures):
477 483 if self.nrows == 0 or self.nplots == 0:
478 484 log.warning('No data', self.name)
479 485 fig.text(0.5, 0.5, 'No Data', fontsize='large', ha='center')
480 486 fig.canvas.manager.set_window_title(self.CODE)
481 487 continue
482 488
483 489 fig.tight_layout()
484 490 fig.canvas.manager.set_window_title('{} - {}'.format(self.title,
485 491 self.getDateTime(self.max_time).strftime('%Y/%m/%d')))
486 492 fig.canvas.draw()
487 493
488 494 if self.save and (self.data.ended or not self.data.buffering):
489 495
490 496 if self.save_labels:
491 497 labels = self.save_labels
492 498 else:
493 499 labels = range(self.nrows)
494 500
495 501 if self.oneFigure:
496 502 label = ''
497 503 else:
498 504 label = '-{}'.format(labels[n])
499 505 figname = os.path.join(
500 506 self.save,
501 507 self.CODE,
502 508 '{}{}_{}.png'.format(
503 509 self.CODE,
504 510 label,
505 511 self.getDateTime(self.saveTime).strftime(
506 512 '%Y%m%d_%H%M%S'),
507 513 )
508 514 )
509 515 log.log('Saving figure: {}'.format(figname), self.name)
510 516 if not os.path.isdir(os.path.dirname(figname)):
511 517 os.makedirs(os.path.dirname(figname))
512 518 fig.savefig(figname)
513 519
514 520 def plot(self):
515 521 '''
516 522 '''
517 523 raise(NotImplementedError, 'Implement this method in child class')
518 524
519 525 def run(self):
520 526
521 527 log.log('Starting', self.name)
522 528
523 529 context = zmq.Context()
524 530 receiver = context.socket(zmq.SUB)
525 531 receiver.setsockopt(zmq.SUBSCRIBE, '')
526 532 receiver.setsockopt(zmq.CONFLATE, self.CONFLATE)
527 533
528 534 if 'server' in self.kwargs['parent']:
529 535 receiver.connect(
530 536 'ipc:///tmp/{}.plots'.format(self.kwargs['parent']['server']))
531 537 else:
532 538 receiver.connect("ipc:///tmp/zmq.plots")
533 539
534 540 while True:
535 541 try:
536 542 self.data = receiver.recv_pyobj(flags=zmq.NOBLOCK)
537 543 if self.data.localtime and self.localtime:
538 544 self.times = self.data.times
539 545 elif self.data.localtime and not self.localtime:
540 546 self.times = self.data.times + time.timezone
541 547 elif not self.data.localtime and self.localtime:
542 548 self.times = self.data.times - time.timezone
543 549 else:
544 550 self.times = self.data.times
545 551
546 552 self.min_time = self.times[0]
547 553 self.max_time = self.times[-1]
548 554
549 555 if self.isConfig is False:
550 556 self.__setup()
551 557 self.isConfig = True
552 558
553 559 self.__plot()
554 560
555 561 except zmq.Again as e:
556 562 if self.data and self.data.ended:
557 563 break
558 564 log.log('Waiting for data...')
559 565 if self.data:
560 566 figpause(self.data.throttle)
561 567 else:
562 568 time.sleep(2)
563 569
564 570 def close(self):
565 571 if self.data:
566 572 self.__plot()
567 573
568 574
569 575 class PlotSpectraData(PlotData):
570 576 '''
571 577 Plot for Spectra data
572 578 '''
573 579
574 580 CODE = 'spc'
575 581 colormap = 'jro'
576 582
577 583 def setup(self):
578 584 self.nplots = len(self.data.channels)
579 585 self.ncols = int(numpy.sqrt(self.nplots) + 0.9)
580 586 self.nrows = int((1.0 * self.nplots / self.ncols) + 0.9)
581 587 self.width = 3.4 * self.ncols
582 588 self.height = 3 * self.nrows
583 589 self.cb_label = 'dB'
584 590 if self.showprofile:
585 591 self.width += 0.8 * self.ncols
586 592
587 593 self.ylabel = 'Range [km]'
588 594
589 595 def plot(self):
590 596 if self.xaxis == "frequency":
591 597 x = self.data.xrange[0]
592 598 self.xlabel = "Frequency (kHz)"
593 599 elif self.xaxis == "time":
594 600 x = self.data.xrange[1]
595 601 self.xlabel = "Time (ms)"
596 602 else:
597 603 x = self.data.xrange[2]
598 604 self.xlabel = "Velocity (m/s)"
599 605
600 606 if self.CODE == 'spc_mean':
601 607 x = self.data.xrange[2]
602 608 self.xlabel = "Velocity (m/s)"
603 609
604 610 self.titles = []
605 611
606 612 y = self.data.heights
607 613 self.y = y
608 614 z = self.data['spc']
609 615
610 616 for n, ax in enumerate(self.axes):
611 617 noise = self.data['noise'][n][-1]
612 618 if self.CODE == 'spc_mean':
613 619 mean = self.data['mean'][n][-1]
614 620 if ax.firsttime:
615 621 self.xmax = self.xmax if self.xmax else numpy.nanmax(x)
616 622 self.xmin = self.xmin if self.xmin else -self.xmax
617 623 self.zmin = self.zmin if self.zmin else numpy.nanmin(z)
618 624 self.zmax = self.zmax if self.zmax else numpy.nanmax(z)
619 625 ax.plt = ax.pcolormesh(x, y, z[n].T,
620 626 vmin=self.zmin,
621 627 vmax=self.zmax,
622 628 cmap=plt.get_cmap(self.colormap)
623 629 )
624 630
625 631 if self.showprofile:
626 632 ax.plt_profile = self.pf_axes[n].plot(
627 633 self.data['rti'][n][-1], y)[0]
628 634 ax.plt_noise = self.pf_axes[n].plot(numpy.repeat(noise, len(y)), y,
629 635 color="k", linestyle="dashed", lw=1)[0]
630 636 if self.CODE == 'spc_mean':
631 637 ax.plt_mean = ax.plot(mean, y, color='k')[0]
632 638 else:
633 639 ax.plt.set_array(z[n].T.ravel())
634 640 if self.showprofile:
635 641 ax.plt_profile.set_data(self.data['rti'][n][-1], y)
636 642 ax.plt_noise.set_data(numpy.repeat(noise, len(y)), y)
637 643 if self.CODE == 'spc_mean':
638 644 ax.plt_mean.set_data(mean, y)
639 645
640 646 self.titles.append('CH {}: {:3.2f}dB'.format(n, noise))
641 647 self.saveTime = self.max_time
642 648
643 649
644 650 class PlotCrossSpectraData(PlotData):
645 651
646 652 CODE = 'cspc'
647 653 zmin_coh = None
648 654 zmax_coh = None
649 655 zmin_phase = None
650 656 zmax_phase = None
651 657
652 658 def setup(self):
653 659
654 660 self.ncols = 4
655 661 self.nrows = len(self.data.pairs)
656 662 self.nplots = self.nrows * 4
657 663 self.width = 3.4 * self.ncols
658 664 self.height = 3 * self.nrows
659 665 self.ylabel = 'Range [km]'
660 666 self.showprofile = False
661 667
662 668 def plot(self):
663 669
664 670 if self.xaxis == "frequency":
665 671 x = self.data.xrange[0]
666 672 self.xlabel = "Frequency (kHz)"
667 673 elif self.xaxis == "time":
668 674 x = self.data.xrange[1]
669 675 self.xlabel = "Time (ms)"
670 676 else:
671 677 x = self.data.xrange[2]
672 678 self.xlabel = "Velocity (m/s)"
673 679
674 680 self.titles = []
675 681
676 682 y = self.data.heights
677 683 self.y = y
678 684 spc = self.data['spc']
679 685 cspc = self.data['cspc']
680 686
681 687 for n in range(self.nrows):
682 688 noise = self.data['noise'][n][-1]
683 689 pair = self.data.pairs[n]
684 690 ax = self.axes[4 * n]
685 691 ax3 = self.axes[4 * n + 3]
686 692 if ax.firsttime:
687 693 self.xmax = self.xmax if self.xmax else numpy.nanmax(x)
688 694 self.xmin = self.xmin if self.xmin else -self.xmax
689 695 self.zmin = self.zmin if self.zmin else numpy.nanmin(spc)
690 696 self.zmax = self.zmax if self.zmax else numpy.nanmax(spc)
691 697 ax.plt = ax.pcolormesh(x, y, spc[pair[0]].T,
692 698 vmin=self.zmin,
693 699 vmax=self.zmax,
694 700 cmap=plt.get_cmap(self.colormap)
695 701 )
696 702 else:
697 703 ax.plt.set_array(spc[pair[0]].T.ravel())
698 704 self.titles.append('CH {}: {:3.2f}dB'.format(n, noise))
699 705
700 706 ax = self.axes[4 * n + 1]
701 707 if ax.firsttime:
702 708 ax.plt = ax.pcolormesh(x, y, spc[pair[1]].T,
703 709 vmin=self.zmin,
704 710 vmax=self.zmax,
705 711 cmap=plt.get_cmap(self.colormap)
706 712 )
707 713 else:
708 714 ax.plt.set_array(spc[pair[1]].T.ravel())
709 715 self.titles.append('CH {}: {:3.2f}dB'.format(n, noise))
710 716
711 717 out = cspc[n] / numpy.sqrt(spc[pair[0]] * spc[pair[1]])
712 718 coh = numpy.abs(out)
713 719 phase = numpy.arctan2(out.imag, out.real) * 180 / numpy.pi
714 720
715 721 ax = self.axes[4 * n + 2]
716 722 if ax.firsttime:
717 723 ax.plt = ax.pcolormesh(x, y, coh.T,
718 724 vmin=0,
719 725 vmax=1,
720 726 cmap=plt.get_cmap(self.colormap_coh)
721 727 )
722 728 else:
723 729 ax.plt.set_array(coh.T.ravel())
724 730 self.titles.append(
725 731 'Coherence Ch{} * Ch{}'.format(pair[0], pair[1]))
726 732
727 733 ax = self.axes[4 * n + 3]
728 734 if ax.firsttime:
729 735 ax.plt = ax.pcolormesh(x, y, phase.T,
730 736 vmin=-180,
731 737 vmax=180,
732 738 cmap=plt.get_cmap(self.colormap_phase)
733 739 )
734 740 else:
735 741 ax.plt.set_array(phase.T.ravel())
736 742 self.titles.append('Phase CH{} * CH{}'.format(pair[0], pair[1]))
737 743
738 744 self.saveTime = self.max_time
739 745
740 746
741 747 class PlotSpectraMeanData(PlotSpectraData):
742 748 '''
743 749 Plot for Spectra and Mean
744 750 '''
745 751 CODE = 'spc_mean'
746 752 colormap = 'jro'
747 753
748 754
749 755 class PlotRTIData(PlotData):
750 756 '''
751 757 Plot for RTI data
752 758 '''
753 759
754 760 CODE = 'rti'
755 761 colormap = 'jro'
756 762
757 763 def setup(self):
758 764 self.xaxis = 'time'
759 765 self.ncols = 1
760 766 self.nrows = len(self.data.channels)
761 767 self.nplots = len(self.data.channels)
762 768 self.ylabel = 'Range [km]'
763 769 self.cb_label = 'dB'
764 770 self.titles = ['{} Channel {}'.format(
765 771 self.CODE.upper(), x) for x in range(self.nrows)]
766 772
767 773 def plot(self):
768 774 self.x = self.times
769 775 self.y = self.data.heights
770 776 self.z = self.data[self.CODE]
771 777 self.z = numpy.ma.masked_invalid(self.z)
772 778
773 779 if self.decimation is None:
774 780 x, y, z = self.fill_gaps(self.x, self.y, self.z)
775 781 else:
776 782 x, y, z = self.fill_gaps(*self.decimate())
777 783
778 784 for n, ax in enumerate(self.axes):
779 785 self.zmin = self.zmin if self.zmin else numpy.min(self.z)
780 786 self.zmax = self.zmax if self.zmax else numpy.max(self.z)
781 787 if ax.firsttime:
782 788 ax.plt = ax.pcolormesh(x, y, z[n].T,
783 789 vmin=self.zmin,
784 790 vmax=self.zmax,
785 791 cmap=plt.get_cmap(self.colormap)
786 792 )
787 793 if self.showprofile:
788 794 ax.plot_profile = self.pf_axes[n].plot(
789 795 self.data['rti'][n][-1], self.y)[0]
790 796 ax.plot_noise = self.pf_axes[n].plot(numpy.repeat(self.data['noise'][n][-1], len(self.y)), self.y,
791 797 color="k", linestyle="dashed", lw=1)[0]
792 798 else:
793 799 ax.collections.remove(ax.collections[0])
794 800 ax.plt = ax.pcolormesh(x, y, z[n].T,
795 801 vmin=self.zmin,
796 802 vmax=self.zmax,
797 803 cmap=plt.get_cmap(self.colormap)
798 804 )
799 805 if self.showprofile:
800 806 ax.plot_profile.set_data(self.data['rti'][n][-1], self.y)
801 807 ax.plot_noise.set_data(numpy.repeat(
802 808 self.data['noise'][n][-1], len(self.y)), self.y)
803 809
804 810 self.saveTime = self.min_time
805 811
806 812
807 813 class PlotCOHData(PlotRTIData):
808 814 '''
809 815 Plot for Coherence data
810 816 '''
811 817
812 818 CODE = 'coh'
813 819
814 820 def setup(self):
815 821 self.xaxis = 'time'
816 822 self.ncols = 1
817 823 self.nrows = len(self.data.pairs)
818 824 self.nplots = len(self.data.pairs)
819 825 self.ylabel = 'Range [km]'
820 826 if self.CODE == 'coh':
821 827 self.cb_label = ''
822 828 self.titles = [
823 829 'Coherence Map Ch{} * Ch{}'.format(x[0], x[1]) for x in self.data.pairs]
824 830 else:
825 831 self.cb_label = 'Degrees'
826 832 self.titles = [
827 833 'Phase Map Ch{} * Ch{}'.format(x[0], x[1]) for x in self.data.pairs]
828 834
829 835
830 836 class PlotPHASEData(PlotCOHData):
831 837 '''
832 838 Plot for Phase map data
833 839 '''
834 840
835 841 CODE = 'phase'
836 842 colormap = 'seismic'
837 843
838 844
839 845 class PlotNoiseData(PlotData):
840 846 '''
841 847 Plot for noise
842 848 '''
843 849
844 850 CODE = 'noise'
845 851
846 852 def setup(self):
847 853 self.xaxis = 'time'
848 854 self.ncols = 1
849 855 self.nrows = 1
850 856 self.nplots = 1
851 857 self.ylabel = 'Intensity [dB]'
852 858 self.titles = ['Noise']
853 859 self.colorbar = False
854 860
855 861 def plot(self):
856 862
857 863 x = self.times
858 864 xmin = self.min_time
859 865 xmax = xmin + self.xrange * 60 * 60
860 866 Y = self.data[self.CODE]
861 867
862 868 if self.axes[0].firsttime:
863 869 for ch in self.data.channels:
864 870 y = Y[ch]
865 871 self.axes[0].plot(x, y, lw=1, label='Ch{}'.format(ch))
866 872 plt.legend()
867 873 else:
868 874 for ch in self.data.channels:
869 875 y = Y[ch]
870 876 self.axes[0].lines[ch].set_data(x, y)
871 877
872 878 self.ymin = numpy.nanmin(Y) - 5
873 879 self.ymax = numpy.nanmax(Y) + 5
874 880 self.saveTime = self.min_time
875 881
876 882
877 883 class PlotSNRData(PlotRTIData):
878 884 '''
879 885 Plot for SNR Data
880 886 '''
881 887
882 888 CODE = 'snr'
883 889 colormap = 'jet'
884 890
885 891
886 892 class PlotDOPData(PlotRTIData):
887 893 '''
888 894 Plot for DOPPLER Data
889 895 '''
890 896
891 897 CODE = 'dop'
892 898 colormap = 'jet'
893 899
894 900
895 901 class PlotSkyMapData(PlotData):
896 902 '''
897 903 Plot for meteors detection data
898 904 '''
899 905
900 906 CODE = 'param'
901 907
902 908 def setup(self):
903 909
904 910 self.ncols = 1
905 911 self.nrows = 1
906 912 self.width = 7.2
907 913 self.height = 7.2
908 914 self.nplots = 1
909 915 self.xlabel = 'Zonal Zenith Angle (deg)'
910 916 self.ylabel = 'Meridional Zenith Angle (deg)'
911 917 self.polar = True
912 918 self.ymin = -180
913 919 self.ymax = 180
914 920 self.colorbar = False
915 921
916 922 def plot(self):
917 923
918 924 arrayParameters = numpy.concatenate(self.data['param'])
919 925 error = arrayParameters[:, -1]
920 926 indValid = numpy.where(error == 0)[0]
921 927 finalMeteor = arrayParameters[indValid, :]
922 928 finalAzimuth = finalMeteor[:, 3]
923 929 finalZenith = finalMeteor[:, 4]
924 930
925 931 x = finalAzimuth * numpy.pi / 180
926 932 y = finalZenith
927 933
928 934 ax = self.axes[0]
929 935
930 936 if ax.firsttime:
931 937 ax.plot = ax.plot(x, y, 'bo', markersize=5)[0]
932 938 else:
933 939 ax.plot.set_data(x, y)
934 940
935 941 dt1 = self.getDateTime(self.min_time).strftime('%y/%m/%d %H:%M:%S')
936 942 dt2 = self.getDateTime(self.max_time).strftime('%y/%m/%d %H:%M:%S')
937 943 title = 'Meteor Detection Sky Map\n %s - %s \n Number of events: %5.0f\n' % (dt1,
938 944 dt2,
939 945 len(x))
940 946 self.titles[0] = title
941 947 self.saveTime = self.max_time
942 948
943 949
944 950 class PlotParamData(PlotRTIData):
945 951 '''
946 952 Plot for data_param object
947 953 '''
948 954
949 955 CODE = 'param'
950 956 colormap = 'seismic'
951 957
952 958 def setup(self):
953 959 self.xaxis = 'time'
954 960 self.ncols = 1
955 961 self.nrows = self.data.shape(self.CODE)[0]
956 962 self.nplots = self.nrows
957 963 if self.showSNR:
958 964 self.nrows += 1
959 965 self.nplots += 1
960 966
961 967 self.ylabel = 'Height [km]'
962 968 if not self.titles:
963 969 self.titles = self.data.parameters \
964 970 if self.data.parameters else ['Param {}'.format(x) for x in xrange(self.nrows)]
965 971 if self.showSNR:
966 972 self.titles.append('SNR')
967 973
968 974 def plot(self):
969 975 self.data.normalize_heights()
970 976 self.x = self.times
971 977 self.y = self.data.heights
972 978 if self.showSNR:
973 979 self.z = numpy.concatenate(
974 980 (self.data[self.CODE], self.data['snr'])
975 981 )
976 982 else:
977 983 self.z = self.data[self.CODE]
978 984
979 985 self.z = numpy.ma.masked_invalid(self.z)
980 986
981 987 if self.decimation is None:
982 988 x, y, z = self.fill_gaps(self.x, self.y, self.z)
983 989 else:
984 990 x, y, z = self.fill_gaps(*self.decimate())
985 991
986 992 for n, ax in enumerate(self.axes):
987 993
988 994 self.zmax = self.zmax if self.zmax is not None else numpy.max(
989 995 self.z[n])
990 996 self.zmin = self.zmin if self.zmin is not None else numpy.min(
991 997 self.z[n])
992 998
993 999 if ax.firsttime:
994 1000 if self.zlimits is not None:
995 1001 self.zmin, self.zmax = self.zlimits[n]
996 1002
997 1003 ax.plt = ax.pcolormesh(x, y, z[n].T * self.factors[n],
998 1004 vmin=self.zmin,
999 1005 vmax=self.zmax,
1000 1006 cmap=self.cmaps[n]
1001 1007 )
1002 1008 else:
1003 1009 if self.zlimits is not None:
1004 1010 self.zmin, self.zmax = self.zlimits[n]
1005 1011 ax.collections.remove(ax.collections[0])
1006 1012 ax.plt = ax.pcolormesh(x, y, z[n].T * self.factors[n],
1007 1013 vmin=self.zmin,
1008 1014 vmax=self.zmax,
1009 1015 cmap=self.cmaps[n]
1010 1016 )
1011 1017
1012 1018 self.saveTime = self.min_time
1013 1019
1014 1020
1015 1021 class PlotOutputData(PlotParamData):
1016 1022 '''
1017 1023 Plot data_output object
1018 1024 '''
1019 1025
1020 1026 CODE = 'output'
1021 1027 colormap = 'seismic'
1022 1028
1023 1029
1024 1030 class PlotPolarMapData(PlotData):
1025 1031 '''
1026 1032 Plot for meteors detection data
1027 1033 '''
1028 1034
1029 1035 CODE = 'param'
1030 1036 colormap = 'seismic'
1031 1037
1032 1038 def setup(self):
1033 1039 self.ncols = 1
1034 1040 self.nrows = 1
1035 1041 self.width = 9
1036 1042 self.height = 8
1037 1043 self.mode = self.data.meta['mode']
1038 1044 if self.channels is not None:
1039 1045 self.nplots = len(self.channels)
1040 1046 self.nrows = len(self.channels)
1041 1047 else:
1042 1048 self.nplots = self.data.shape(self.CODE)[0]
1043 1049 self.nrows = self.nplots
1044 1050 self.channels = range(self.nplots)
1045 1051 if self.mode == 'E':
1046 1052 self.xlabel = 'Longitude'
1047 1053 self.ylabel = 'Latitude'
1048 1054 else:
1049 1055 self.xlabel = 'Range (km)'
1050 1056 self.ylabel = 'Height (km)'
1051 1057 self.bgcolor = 'white'
1052 1058 self.cb_labels = self.data.meta['units']
1053 1059 self.lat = self.data.meta['latitude']
1054 1060 self.lon = self.data.meta['longitude']
1055 1061 self.xmin, self.xmax = float(km2deg(self.xmin) + self.lon), float(km2deg(self.xmax) + self.lon)
1056 1062 self.ymin, self.ymax = float(km2deg(self.ymin) + self.lat), float(km2deg(self.ymax) + self.lat)
1057 1063 # self.polar = True
1058 1064
1059 1065 def plot(self):
1060 1066
1061 1067 for n, ax in enumerate(self.axes):
1062 1068 data = self.data['param'][self.channels[n]]
1063 1069
1064 1070 zeniths = numpy.linspace(0, self.data.meta['max_range'], data.shape[1])
1065 1071 if self.mode == 'E':
1066 1072 azimuths = -numpy.radians(self.data.heights)+numpy.pi/2
1067 1073 r, theta = numpy.meshgrid(zeniths, azimuths)
1068 1074 x, y = r*numpy.cos(theta)*numpy.cos(numpy.radians(self.data.meta['elevation'])), r*numpy.sin(theta)*numpy.cos(numpy.radians(self.data.meta['elevation']))
1069 1075 x = km2deg(x) + self.lon
1070 1076 y = km2deg(y) + self.lat
1071 1077 else:
1072 1078 azimuths = numpy.radians(self.data.heights)
1073 1079 r, theta = numpy.meshgrid(zeniths, azimuths)
1074 1080 x, y = r*numpy.cos(theta), r*numpy.sin(theta)
1075 1081 self.y = zeniths
1076 1082
1077 1083 if ax.firsttime:
1078 1084 if self.zlimits is not None:
1079 1085 self.zmin, self.zmax = self.zlimits[n]
1080 1086 ax.plt = ax.pcolormesh(#r, theta, numpy.ma.array(data, mask=numpy.isnan(data)),
1081 1087 x, y, numpy.ma.array(data, mask=numpy.isnan(data)),
1082 1088 vmin=self.zmin,
1083 1089 vmax=self.zmax,
1084 1090 cmap=self.cmaps[n])
1085 1091 else:
1086 1092 if self.zlimits is not None:
1087 1093 self.zmin, self.zmax = self.zlimits[n]
1088 1094 ax.collections.remove(ax.collections[0])
1089 1095 ax.plt = ax.pcolormesh(# r, theta, numpy.ma.array(data, mask=numpy.isnan(data)),
1090 1096 x, y, numpy.ma.array(data, mask=numpy.isnan(data)),
1091 1097 vmin=self.zmin,
1092 1098 vmax=self.zmax,
1093 1099 cmap=self.cmaps[n])
1094 1100
1095 1101 if self.mode == 'A':
1096 1102 continue
1097 1103
1098 1104 # plot district names
1099 1105 f = open('/data/workspace/schain_scripts/distrito.csv')
1100 1106 for line in f:
1101 1107 label, lon, lat = [s.strip() for s in line.split(',') if s]
1102 1108 lat = float(lat)
1103 1109 lon = float(lon)
1104 1110 # ax.plot(lon, lat, '.b', ms=2)
1105 1111 ax.text(lon, lat, label.decode('utf8'), ha='center', va='bottom', size='8', color='black')
1106 1112
1107 1113 # plot limites
1108 1114 limites =[]
1109 1115 tmp = []
1110 1116 for line in open('/data/workspace/schain_scripts/lima.csv'):
1111 1117 if '#' in line:
1112 1118 if tmp:
1113 1119 limites.append(tmp)
1114 1120 tmp = []
1115 1121 continue
1116 1122 values = line.strip().split(',')
1117 1123 tmp.append((float(values[0]), float(values[1])))
1118 1124 for points in limites:
1119 1125 ax.add_patch(Polygon(points, ec='k', fc='none', ls='--', lw=0.5))
1120 1126
1121 1127 # plot Cuencas
1122 1128 for cuenca in ('rimac', 'lurin', 'mala', 'chillon', 'chilca', 'chancay-huaral'):
1123 1129 f = open('/data/workspace/schain_scripts/{}.csv'.format(cuenca))
1124 1130 values = [line.strip().split(',') for line in f]
1125 1131 points = [(float(s[0]), float(s[1])) for s in values]
1126 1132 ax.add_patch(Polygon(points, ec='b', fc='none'))
1127 1133
1128 1134 # plot grid
1129 1135 for r in (15, 30, 45, 60):
1130 1136 ax.add_artist(plt.Circle((self.lon, self.lat), km2deg(r), color='0.6', fill=False, lw=0.2))
1131 1137 ax.text(
1132 1138 self.lon + (km2deg(r))*numpy.cos(60*numpy.pi/180),
1133 1139 self.lat + (km2deg(r))*numpy.sin(60*numpy.pi/180),
1134 1140 '{}km'.format(r),
1135 1141 ha='center', va='bottom', size='8', color='0.6', weight='heavy')
1136 1142
1137 1143 if self.mode == 'E':
1138 1144 title = 'El={}$^\circ$'.format(self.data.meta['elevation'])
1139 1145 label = 'E{:02d}'.format(int(self.data.meta['elevation']))
1140 1146 else:
1141 1147 title = 'Az={}$^\circ$'.format(self.data.meta['azimuth'])
1142 1148 label = 'A{:02d}'.format(int(self.data.meta['azimuth']))
1143 1149
1144 1150 self.save_labels = ['{}-{}'.format(lbl, label) for lbl in self.labels]
1145 1151 self.titles = ['{} {}'.format(self.data.parameters[x], title) for x in self.channels]
1146 1152 self.saveTime = self.max_time
1147 1153
1148 1154
@@ -1,679 +1,679
1 1 '''
2 2 Created on Jul 2, 2014
3 3
4 4 @author: roj-idl71
5 5 '''
6 6 import numpy
7 7
8 8 from jroIO_base import LOCALTIME, JRODataReader, JRODataWriter
9 9 from schainpy.model.proc.jroproc_base import ProcessingUnit, Operation
10 10 from schainpy.model.data.jroheaderIO import PROCFLAG, BasicHeader, SystemHeader, RadarControllerHeader, ProcessingHeader
11 11 from schainpy.model.data.jrodata import Spectra
12 12
13 13 class SpectraReader(JRODataReader, ProcessingUnit):
14 14 """
15 15 Esta clase permite leer datos de espectros desde archivos procesados (.pdata). La lectura
16 16 de los datos siempre se realiza por bloques. Los datos leidos (array de 3 dimensiones)
17 17 son almacenados en tres buffer's para el Self Spectra, el Cross Spectra y el DC Channel.
18 18
19 19 paresCanalesIguales * alturas * perfiles (Self Spectra)
20 20 paresCanalesDiferentes * alturas * perfiles (Cross Spectra)
21 21 canales * alturas (DC Channels)
22 22
23 23 Esta clase contiene instancias (objetos) de las clases BasicHeader, SystemHeader,
24 24 RadarControllerHeader y Spectra. Los tres primeros se usan para almacenar informacion de la
25 25 cabecera de datos (metadata), y el cuarto (Spectra) para obtener y almacenar un bloque de
26 26 datos desde el "buffer" cada vez que se ejecute el metodo "getData".
27 27
28 28 Example:
29 29 dpath = "/home/myuser/data"
30 30
31 31 startTime = datetime.datetime(2010,1,20,0,0,0,0,0,0)
32 32
33 33 endTime = datetime.datetime(2010,1,21,23,59,59,0,0,0)
34 34
35 35 readerObj = SpectraReader()
36 36
37 37 readerObj.setup(dpath, startTime, endTime)
38 38
39 39 while(True):
40 40
41 41 readerObj.getData()
42 42
43 43 print readerObj.data_spc
44 44
45 45 print readerObj.data_cspc
46 46
47 47 print readerObj.data_dc
48 48
49 49 if readerObj.flagNoMoreFiles:
50 50 break
51 51
52 52 """
53 53
54 54 pts2read_SelfSpectra = 0
55 55
56 56 pts2read_CrossSpectra = 0
57 57
58 58 pts2read_DCchannels = 0
59 59
60 60 ext = ".pdata"
61 61
62 62 optchar = "P"
63 63
64 64 dataOut = None
65 65
66 66 nRdChannels = None
67 67
68 68 nRdPairs = None
69 69
70 70 rdPairList = []
71 71
72 72 def __init__(self, **kwargs):
73 73 """
74 74 Inicializador de la clase SpectraReader para la lectura de datos de espectros.
75 75
76 76 Inputs:
77 77 dataOut : Objeto de la clase Spectra. Este objeto sera utilizado para
78 78 almacenar un perfil de datos cada vez que se haga un requerimiento
79 79 (getData). El perfil sera obtenido a partir del buffer de datos,
80 80 si el buffer esta vacio se hara un nuevo proceso de lectura de un
81 81 bloque de datos.
82 82 Si este parametro no es pasado se creara uno internamente.
83 83
84 84 Affected:
85 85 self.dataOut
86 86
87 87 Return : None
88 88 """
89 89
90 90 #Eliminar de la base la herencia
91 91 ProcessingUnit.__init__(self, **kwargs)
92 92
93 93 # self.isConfig = False
94 94
95 95 self.pts2read_SelfSpectra = 0
96 96
97 97 self.pts2read_CrossSpectra = 0
98 98
99 99 self.pts2read_DCchannels = 0
100 100
101 101 self.datablock = None
102 102
103 103 self.utc = None
104 104
105 105 self.ext = ".pdata"
106 106
107 107 self.optchar = "P"
108 108
109 109 self.basicHeaderObj = BasicHeader(LOCALTIME)
110 110
111 111 self.systemHeaderObj = SystemHeader()
112 112
113 113 self.radarControllerHeaderObj = RadarControllerHeader()
114 114
115 115 self.processingHeaderObj = ProcessingHeader()
116 116
117 117 self.online = 0
118 118
119 119 self.fp = None
120 120
121 121 self.idFile = None
122 122
123 123 self.dtype = None
124 124
125 125 self.fileSizeByHeader = None
126 126
127 127 self.filenameList = []
128 128
129 129 self.filename = None
130 130
131 131 self.fileSize = None
132 132
133 133 self.firstHeaderSize = 0
134 134
135 135 self.basicHeaderSize = 24
136 136
137 137 self.pathList = []
138 138
139 139 self.lastUTTime = 0
140 140
141 141 self.maxTimeStep = 30
142 142
143 143 self.flagNoMoreFiles = 0
144 144
145 145 self.set = 0
146 146
147 147 self.path = None
148 148
149 149 self.delay = 60 #seconds
150 150
151 151 self.nTries = 3 #quantity tries
152 152
153 153 self.nFiles = 3 #number of files for searching
154 154
155 155 self.nReadBlocks = 0
156 156
157 157 self.flagIsNewFile = 1
158 158
159 159 self.__isFirstTimeOnline = 1
160 160
161 161 # self.ippSeconds = 0
162 162
163 163 self.flagDiscontinuousBlock = 0
164 164
165 165 self.flagIsNewBlock = 0
166 166
167 167 self.nTotalBlocks = 0
168 168
169 169 self.blocksize = 0
170 170
171 171 self.dataOut = self.createObjByDefault()
172 172
173 173 self.profileIndex = 1 #Always
174 174
175 175
176 176 def createObjByDefault(self):
177 177
178 178 dataObj = Spectra()
179 179
180 180 return dataObj
181 181
182 182 def __hasNotDataInBuffer(self):
183 183 return 1
184 184
185 185
186 186 def getBlockDimension(self):
187 187 """
188 188 Obtiene la cantidad de puntos a leer por cada bloque de datos
189 189
190 190 Affected:
191 191 self.nRdChannels
192 192 self.nRdPairs
193 193 self.pts2read_SelfSpectra
194 194 self.pts2read_CrossSpectra
195 195 self.pts2read_DCchannels
196 196 self.blocksize
197 197 self.dataOut.nChannels
198 198 self.dataOut.nPairs
199 199
200 200 Return:
201 201 None
202 202 """
203 203 self.nRdChannels = 0
204 204 self.nRdPairs = 0
205 205 self.rdPairList = []
206 206
207 207 for i in range(0, self.processingHeaderObj.totalSpectra*2, 2):
208 208 if self.processingHeaderObj.spectraComb[i] == self.processingHeaderObj.spectraComb[i+1]:
209 209 self.nRdChannels = self.nRdChannels + 1 #par de canales iguales
210 210 else:
211 211 self.nRdPairs = self.nRdPairs + 1 #par de canales diferentes
212 212 self.rdPairList.append((self.processingHeaderObj.spectraComb[i], self.processingHeaderObj.spectraComb[i+1]))
213 213
214 214 pts2read = self.processingHeaderObj.nHeights * self.processingHeaderObj.profilesPerBlock
215 215
216 216 self.pts2read_SelfSpectra = int(self.nRdChannels * pts2read)
217 217 self.blocksize = self.pts2read_SelfSpectra
218 218
219 219 if self.processingHeaderObj.flag_cspc:
220 220 self.pts2read_CrossSpectra = int(self.nRdPairs * pts2read)
221 221 self.blocksize += self.pts2read_CrossSpectra
222 222
223 223 if self.processingHeaderObj.flag_dc:
224 224 self.pts2read_DCchannels = int(self.systemHeaderObj.nChannels * self.processingHeaderObj.nHeights)
225 225 self.blocksize += self.pts2read_DCchannels
226 226
227 227 # self.blocksize = self.pts2read_SelfSpectra + self.pts2read_CrossSpectra + self.pts2read_DCchannels
228 228
229 229
230 230 def readBlock(self):
231 231 """
232 232 Lee el bloque de datos desde la posicion actual del puntero del archivo
233 233 (self.fp) y actualiza todos los parametros relacionados al bloque de datos
234 234 (metadata + data). La data leida es almacenada en el buffer y el contador del buffer
235 235 es seteado a 0
236 236
237 237 Return: None
238 238
239 239 Variables afectadas:
240 240
241 241 self.flagIsNewFile
242 242 self.flagIsNewBlock
243 243 self.nTotalBlocks
244 244 self.data_spc
245 245 self.data_cspc
246 246 self.data_dc
247 247
248 248 Exceptions:
249 249 Si un bloque leido no es un bloque valido
250 250 """
251 251 blockOk_flag = False
252 252 fpointer = self.fp.tell()
253 253
254 254 spc = numpy.fromfile( self.fp, self.dtype[0], self.pts2read_SelfSpectra )
255 255 spc = spc.reshape( (self.nRdChannels, self.processingHeaderObj.nHeights, self.processingHeaderObj.profilesPerBlock) ) #transforma a un arreglo 3D
256 256
257 257 if self.processingHeaderObj.flag_cspc:
258 258 cspc = numpy.fromfile( self.fp, self.dtype, self.pts2read_CrossSpectra )
259 259 cspc = cspc.reshape( (self.nRdPairs, self.processingHeaderObj.nHeights, self.processingHeaderObj.profilesPerBlock) ) #transforma a un arreglo 3D
260 260
261 261 if self.processingHeaderObj.flag_dc:
262 262 dc = numpy.fromfile( self.fp, self.dtype, self.pts2read_DCchannels ) #int(self.processingHeaderObj.nHeights*self.systemHeaderObj.nChannels) )
263 263 dc = dc.reshape( (self.systemHeaderObj.nChannels, self.processingHeaderObj.nHeights) ) #transforma a un arreglo 2D
264 264
265 265
266 if self.processingHeaderObj.shif_fft:
266 if not self.processingHeaderObj.shif_fft:
267 267 #desplaza a la derecha en el eje 2 determinadas posiciones
268 268 shift = int(self.processingHeaderObj.profilesPerBlock/2)
269 269 spc = numpy.roll( spc, shift , axis=2 )
270 270
271 271 if self.processingHeaderObj.flag_cspc:
272 272 #desplaza a la derecha en el eje 2 determinadas posiciones
273 273 cspc = numpy.roll( cspc, shift, axis=2 )
274 274
275 275 #Dimensions : nChannels, nProfiles, nSamples
276 276 spc = numpy.transpose( spc, (0,2,1) )
277 277 self.data_spc = spc
278 278
279 279 if self.processingHeaderObj.flag_cspc:
280 280 cspc = numpy.transpose( cspc, (0,2,1) )
281 281 self.data_cspc = cspc['real'] + cspc['imag']*1j
282 282 else:
283 283 self.data_cspc = None
284 284
285 285 if self.processingHeaderObj.flag_dc:
286 286 self.data_dc = dc['real'] + dc['imag']*1j
287 287 else:
288 288 self.data_dc = None
289 289
290 290 self.flagIsNewFile = 0
291 291 self.flagIsNewBlock = 1
292 292
293 293 self.nTotalBlocks += 1
294 294 self.nReadBlocks += 1
295 295
296 296 return 1
297 297
298 298 def getFirstHeader(self):
299 299
300 300 self.getBasicHeader()
301 301
302 302 self.dataOut.systemHeaderObj = self.systemHeaderObj.copy()
303 303
304 304 self.dataOut.radarControllerHeaderObj = self.radarControllerHeaderObj.copy()
305 305
306 306 # self.dataOut.ippSeconds = self.ippSeconds
307 307
308 308 # self.dataOut.timeInterval = self.radarControllerHeaderObj.ippSeconds * self.processingHeaderObj.nCohInt * self.processingHeaderObj.nIncohInt * self.processingHeaderObj.profilesPerBlock
309 309
310 310 self.dataOut.dtype = self.dtype
311 311
312 312 # self.dataOut.nPairs = self.nPairs
313 313
314 314 self.dataOut.pairsList = self.rdPairList
315 315
316 316 self.dataOut.nProfiles = self.processingHeaderObj.profilesPerBlock
317 317
318 318 self.dataOut.nFFTPoints = self.processingHeaderObj.profilesPerBlock
319 319
320 320 self.dataOut.nCohInt = self.processingHeaderObj.nCohInt
321 321
322 322 self.dataOut.nIncohInt = self.processingHeaderObj.nIncohInt
323 323
324 324 xf = self.processingHeaderObj.firstHeight + self.processingHeaderObj.nHeights*self.processingHeaderObj.deltaHeight
325 325
326 326 self.dataOut.heightList = numpy.arange(self.processingHeaderObj.firstHeight, xf, self.processingHeaderObj.deltaHeight)
327 327
328 328 self.dataOut.channelList = range(self.systemHeaderObj.nChannels)
329 329
330 330 self.dataOut.flagShiftFFT = True #Data is always shifted
331 331
332 332 self.dataOut.flagDecodeData = self.processingHeaderObj.flag_decode #asumo q la data no esta decodificada
333 333
334 334 self.dataOut.flagDeflipData = self.processingHeaderObj.flag_deflip #asumo q la data esta sin flip
335 335
336 336 def getData(self):
337 337 """
338 338 First method to execute before "RUN" is called.
339 339
340 340 Copia el buffer de lectura a la clase "Spectra",
341 341 con todos los parametros asociados a este (metadata). cuando no hay datos en el buffer de
342 342 lectura es necesario hacer una nueva lectura de los bloques de datos usando "readNextBlock"
343 343
344 344 Return:
345 345 0 : Si no hay mas archivos disponibles
346 346 1 : Si hizo una buena copia del buffer
347 347
348 348 Affected:
349 349 self.dataOut
350 350
351 351 self.flagDiscontinuousBlock
352 352 self.flagIsNewBlock
353 353 """
354 354
355 355 if self.flagNoMoreFiles:
356 356 self.dataOut.flagNoData = True
357 357 print 'Process finished'
358 358 return 0
359 359
360 360 self.flagDiscontinuousBlock = 0
361 361 self.flagIsNewBlock = 0
362 362
363 363 if self.__hasNotDataInBuffer():
364 364
365 365 if not( self.readNextBlock() ):
366 366 self.dataOut.flagNoData = True
367 367 return 0
368 368
369 369 #data es un numpy array de 3 dmensiones (perfiles, alturas y canales)
370 370
371 371 if self.data_spc is None:
372 372 self.dataOut.flagNoData = True
373 373 return 0
374 374
375 375 self.getBasicHeader()
376 376
377 377 self.getFirstHeader()
378 378
379 379 self.dataOut.data_spc = self.data_spc
380 380
381 381 self.dataOut.data_cspc = self.data_cspc
382 382
383 383 self.dataOut.data_dc = self.data_dc
384 384
385 385 self.dataOut.flagNoData = False
386 386
387 387 self.dataOut.realtime = self.online
388 388
389 389 return self.dataOut.data_spc
390 390
391 391 class SpectraWriter(JRODataWriter, Operation):
392 392
393 393 """
394 394 Esta clase permite escribir datos de espectros a archivos procesados (.pdata). La escritura
395 395 de los datos siempre se realiza por bloques.
396 396 """
397 397
398 398 ext = ".pdata"
399 399
400 400 optchar = "P"
401 401
402 402 shape_spc_Buffer = None
403 403
404 404 shape_cspc_Buffer = None
405 405
406 406 shape_dc_Buffer = None
407 407
408 408 data_spc = None
409 409
410 410 data_cspc = None
411 411
412 412 data_dc = None
413 413
414 414 # dataOut = None
415 415
416 416 def __init__(self, **kwargs):
417 417 """
418 418 Inicializador de la clase SpectraWriter para la escritura de datos de espectros.
419 419
420 420 Affected:
421 421 self.dataOut
422 422 self.basicHeaderObj
423 423 self.systemHeaderObj
424 424 self.radarControllerHeaderObj
425 425 self.processingHeaderObj
426 426
427 427 Return: None
428 428 """
429 429
430 430 Operation.__init__(self, **kwargs)
431 431
432 432 self.isConfig = False
433 433
434 434 self.nTotalBlocks = 0
435 435
436 436 self.data_spc = None
437 437
438 438 self.data_cspc = None
439 439
440 440 self.data_dc = None
441 441
442 442 self.fp = None
443 443
444 444 self.flagIsNewFile = 1
445 445
446 446 self.nTotalBlocks = 0
447 447
448 448 self.flagIsNewBlock = 0
449 449
450 450 self.setFile = None
451 451
452 452 self.dtype = None
453 453
454 454 self.path = None
455 455
456 456 self.noMoreFiles = 0
457 457
458 458 self.filename = None
459 459
460 460 self.basicHeaderObj = BasicHeader(LOCALTIME)
461 461
462 462 self.systemHeaderObj = SystemHeader()
463 463
464 464 self.radarControllerHeaderObj = RadarControllerHeader()
465 465
466 466 self.processingHeaderObj = ProcessingHeader()
467 467
468 468
469 469 def hasAllDataInBuffer(self):
470 470 return 1
471 471
472 472
473 473 def setBlockDimension(self):
474 474 """
475 475 Obtiene las formas dimensionales del los subbloques de datos que componen un bloque
476 476
477 477 Affected:
478 478 self.shape_spc_Buffer
479 479 self.shape_cspc_Buffer
480 480 self.shape_dc_Buffer
481 481
482 482 Return: None
483 483 """
484 484 self.shape_spc_Buffer = (self.dataOut.nChannels,
485 485 self.processingHeaderObj.nHeights,
486 486 self.processingHeaderObj.profilesPerBlock)
487 487
488 488 self.shape_cspc_Buffer = (self.dataOut.nPairs,
489 489 self.processingHeaderObj.nHeights,
490 490 self.processingHeaderObj.profilesPerBlock)
491 491
492 492 self.shape_dc_Buffer = (self.dataOut.nChannels,
493 493 self.processingHeaderObj.nHeights)
494 494
495 495
496 496 def writeBlock(self):
497 497 """
498 498 Escribe el buffer en el file designado
499 499
500 500 Affected:
501 501 self.data_spc
502 502 self.data_cspc
503 503 self.data_dc
504 504 self.flagIsNewFile
505 505 self.flagIsNewBlock
506 506 self.nTotalBlocks
507 507 self.nWriteBlocks
508 508
509 509 Return: None
510 510 """
511 511
512 512 spc = numpy.transpose( self.data_spc, (0,2,1) )
513 if self.processingHeaderObj.shif_fft:
513 if not self.processingHeaderObj.shif_fft:
514 514 spc = numpy.roll( spc, self.processingHeaderObj.profilesPerBlock/2, axis=2 ) #desplaza a la derecha en el eje 2 determinadas posiciones
515 515 data = spc.reshape((-1))
516 516 data = data.astype(self.dtype[0])
517 517 data.tofile(self.fp)
518 518
519 519 if self.data_cspc is not None:
520 520 data = numpy.zeros( self.shape_cspc_Buffer, self.dtype )
521 521 cspc = numpy.transpose( self.data_cspc, (0,2,1) )
522 if self.processingHeaderObj.shif_fft:
522 if not self.processingHeaderObj.shif_fft:
523 523 cspc = numpy.roll( cspc, self.processingHeaderObj.profilesPerBlock/2, axis=2 ) #desplaza a la derecha en el eje 2 determinadas posiciones
524 524 data['real'] = cspc.real
525 525 data['imag'] = cspc.imag
526 526 data = data.reshape((-1))
527 527 data.tofile(self.fp)
528 528
529 529 if self.data_dc is not None:
530 530 data = numpy.zeros( self.shape_dc_Buffer, self.dtype )
531 531 dc = self.data_dc
532 532 data['real'] = dc.real
533 533 data['imag'] = dc.imag
534 534 data = data.reshape((-1))
535 535 data.tofile(self.fp)
536 536
537 537 # self.data_spc.fill(0)
538 538 #
539 539 # if self.data_dc is not None:
540 540 # self.data_dc.fill(0)
541 541 #
542 542 # if self.data_cspc is not None:
543 543 # self.data_cspc.fill(0)
544 544
545 545 self.flagIsNewFile = 0
546 546 self.flagIsNewBlock = 1
547 547 self.nTotalBlocks += 1
548 548 self.nWriteBlocks += 1
549 549 self.blockIndex += 1
550 550
551 551 # print "[Writing] Block = %d04" %self.blockIndex
552 552
553 553 def putData(self):
554 554 """
555 555 Setea un bloque de datos y luego los escribe en un file
556 556
557 557 Affected:
558 558 self.data_spc
559 559 self.data_cspc
560 560 self.data_dc
561 561
562 562 Return:
563 563 0 : Si no hay data o no hay mas files que puedan escribirse
564 564 1 : Si se escribio la data de un bloque en un file
565 565 """
566 566
567 567 if self.dataOut.flagNoData:
568 568 return 0
569 569
570 570 self.flagIsNewBlock = 0
571 571
572 572 if self.dataOut.flagDiscontinuousBlock:
573 573 self.data_spc.fill(0)
574 574 if self.dataOut.data_cspc is not None:
575 575 self.data_cspc.fill(0)
576 576 if self.dataOut.data_dc is not None:
577 577 self.data_dc.fill(0)
578 578 self.setNextFile()
579 579
580 580 if self.flagIsNewFile == 0:
581 581 self.setBasicHeader()
582 582
583 583 self.data_spc = self.dataOut.data_spc.copy()
584 584
585 585 if self.dataOut.data_cspc is not None:
586 586 self.data_cspc = self.dataOut.data_cspc.copy()
587 587
588 588 if self.dataOut.data_dc is not None:
589 589 self.data_dc = self.dataOut.data_dc.copy()
590 590
591 591 # #self.processingHeaderObj.dataBlocksPerFile)
592 592 if self.hasAllDataInBuffer():
593 593 # self.setFirstHeader()
594 594 self.writeNextBlock()
595 595
596 596 return 1
597 597
598 598 def __getBlockSize(self):
599 599 '''
600 600 Este metodos determina el cantidad de bytes para un bloque de datos de tipo Spectra
601 601 '''
602 602
603 603 dtype_width = self.getDtypeWidth()
604 604
605 605 pts2write = self.dataOut.nHeights * self.dataOut.nFFTPoints
606 606
607 607 pts2write_SelfSpectra = int(self.dataOut.nChannels * pts2write)
608 608 blocksize = (pts2write_SelfSpectra*dtype_width)
609 609
610 610 if self.dataOut.data_cspc is not None:
611 611 pts2write_CrossSpectra = int(self.dataOut.nPairs * pts2write)
612 612 blocksize += (pts2write_CrossSpectra*dtype_width*2)
613 613
614 614 if self.dataOut.data_dc is not None:
615 615 pts2write_DCchannels = int(self.dataOut.nChannels * self.dataOut.nHeights)
616 616 blocksize += (pts2write_DCchannels*dtype_width*2)
617 617
618 618 # blocksize = blocksize #* datatypeValue * 2 #CORREGIR ESTO
619 619
620 620 return blocksize
621 621
622 622 def setFirstHeader(self):
623 623
624 624 """
625 625 Obtiene una copia del First Header
626 626
627 627 Affected:
628 628 self.systemHeaderObj
629 629 self.radarControllerHeaderObj
630 630 self.dtype
631 631
632 632 Return:
633 633 None
634 634 """
635 635
636 636 self.systemHeaderObj = self.dataOut.systemHeaderObj.copy()
637 637 self.systemHeaderObj.nChannels = self.dataOut.nChannels
638 638 self.radarControllerHeaderObj = self.dataOut.radarControllerHeaderObj.copy()
639 639
640 640 self.processingHeaderObj.dtype = 1 # Spectra
641 641 self.processingHeaderObj.blockSize = self.__getBlockSize()
642 642 self.processingHeaderObj.profilesPerBlock = self.dataOut.nFFTPoints
643 643 self.processingHeaderObj.dataBlocksPerFile = self.blocksPerFile
644 644 self.processingHeaderObj.nWindows = 1 #podria ser 1 o self.dataOut.processingHeaderObj.nWindows
645 645 self.processingHeaderObj.nCohInt = self.dataOut.nCohInt# Se requiere para determinar el valor de timeInterval
646 646 self.processingHeaderObj.nIncohInt = self.dataOut.nIncohInt
647 647 self.processingHeaderObj.totalSpectra = self.dataOut.nPairs + self.dataOut.nChannels
648 648 self.processingHeaderObj.shif_fft = self.dataOut.flagShiftFFT
649 649
650 650 if self.processingHeaderObj.totalSpectra > 0:
651 651 channelList = []
652 652 for channel in range(self.dataOut.nChannels):
653 653 channelList.append(channel)
654 654 channelList.append(channel)
655 655
656 656 pairsList = []
657 657 if self.dataOut.nPairs > 0:
658 658 for pair in self.dataOut.pairsList:
659 659 pairsList.append(pair[0])
660 660 pairsList.append(pair[1])
661 661
662 662 spectraComb = channelList + pairsList
663 663 spectraComb = numpy.array(spectraComb, dtype="u1")
664 664 self.processingHeaderObj.spectraComb = spectraComb
665 665
666 666 if self.dataOut.code is not None:
667 667 self.processingHeaderObj.code = self.dataOut.code
668 668 self.processingHeaderObj.nCode = self.dataOut.nCode
669 669 self.processingHeaderObj.nBaud = self.dataOut.nBaud
670 670
671 671 if self.processingHeaderObj.nWindows != 0:
672 672 self.processingHeaderObj.firstHeight = self.dataOut.heightList[0]
673 673 self.processingHeaderObj.deltaHeight = self.dataOut.heightList[1] - self.dataOut.heightList[0]
674 674 self.processingHeaderObj.nHeights = self.dataOut.nHeights
675 675 self.processingHeaderObj.samplesWin = self.dataOut.nHeights
676 676
677 677 self.processingHeaderObj.processFlags = self.getProcessFlags()
678 678
679 679 self.setBasicHeader()
General Comments 0
You need to be logged in to leave comments. Login now