##// END OF EJS Templates
modified for reading channels with the same pointing
joabAM -
r1557:a1cf32393853
parent child
Show More
@@ -1,705 +1,705
1 # Copyright (c) 2012-2020 Jicamarca Radio Observatory
1 # Copyright (c) 2012-2020 Jicamarca Radio Observatory
2 # All rights reserved.
2 # All rights reserved.
3 #
3 #
4 # Distributed under the terms of the BSD 3-clause license.
4 # Distributed under the terms of the BSD 3-clause license.
5 """Base class to create plot operations
5 """Base class to create plot operations
6
6
7 """
7 """
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import zmq
11 import zmq
12 import time
12 import time
13 import numpy
13 import numpy
14 import datetime
14 import datetime
15 from collections import deque
15 from collections import deque
16 from functools import wraps
16 from functools import wraps
17 from threading import Thread
17 from threading import Thread
18 import matplotlib
18 import matplotlib
19
19
20 if 'BACKEND' in os.environ:
20 if 'BACKEND' in os.environ:
21 matplotlib.use(os.environ['BACKEND'])
21 matplotlib.use(os.environ['BACKEND'])
22 elif 'linux' in sys.platform:
22 elif 'linux' in sys.platform:
23 matplotlib.use("TkAgg")
23 matplotlib.use("TkAgg")
24 elif 'darwin' in sys.platform:
24 elif 'darwin' in sys.platform:
25 matplotlib.use('MacOSX')
25 matplotlib.use('MacOSX')
26 else:
26 else:
27 from schainpy.utils import log
27 from schainpy.utils import log
28 log.warning('Using default Backend="Agg"', 'INFO')
28 log.warning('Using default Backend="Agg"', 'INFO')
29 matplotlib.use('Agg')
29 matplotlib.use('Agg')
30
30
31 import matplotlib.pyplot as plt
31 import matplotlib.pyplot as plt
32 from matplotlib.patches import Polygon
32 from matplotlib.patches import Polygon
33 from mpl_toolkits.axes_grid1 import make_axes_locatable
33 from mpl_toolkits.axes_grid1 import make_axes_locatable
34 from matplotlib.ticker import FuncFormatter, LinearLocator, MultipleLocator
34 from matplotlib.ticker import FuncFormatter, LinearLocator, MultipleLocator
35
35
36 from schainpy.model.data.jrodata import PlotterData
36 from schainpy.model.data.jrodata import PlotterData
37 from schainpy.model.proc.jroproc_base import ProcessingUnit, Operation, MPDecorator
37 from schainpy.model.proc.jroproc_base import ProcessingUnit, Operation, MPDecorator
38 from schainpy.utils import log
38 from schainpy.utils import log
39
39
40 jet_values = matplotlib.pyplot.get_cmap('jet', 100)(numpy.arange(100))[10:90]
40 jet_values = matplotlib.pyplot.get_cmap('jet', 100)(numpy.arange(100))[10:90]
41 blu_values = matplotlib.pyplot.get_cmap(
41 blu_values = matplotlib.pyplot.get_cmap(
42 'seismic_r', 20)(numpy.arange(20))[10:15]
42 'seismic_r', 20)(numpy.arange(20))[10:15]
43 ncmap = matplotlib.colors.LinearSegmentedColormap.from_list(
43 ncmap = matplotlib.colors.LinearSegmentedColormap.from_list(
44 'jro', numpy.vstack((blu_values, jet_values)))
44 'jro', numpy.vstack((blu_values, jet_values)))
45 matplotlib.pyplot.register_cmap(cmap=ncmap)
45 matplotlib.pyplot.register_cmap(cmap=ncmap)
46
46
47 CMAPS = [plt.get_cmap(s) for s in ('jro', 'jet', 'viridis',
47 CMAPS = [plt.get_cmap(s) for s in ('jro', 'jet', 'viridis',
48 'plasma', 'inferno', 'Greys', 'seismic', 'bwr', 'coolwarm')]
48 'plasma', 'inferno', 'Greys', 'seismic', 'bwr', 'coolwarm')]
49
49
50 EARTH_RADIUS = 6.3710e3
50 EARTH_RADIUS = 6.3710e3
51
51
52 def ll2xy(lat1, lon1, lat2, lon2):
52 def ll2xy(lat1, lon1, lat2, lon2):
53
53
54 p = 0.017453292519943295
54 p = 0.017453292519943295
55 a = 0.5 - numpy.cos((lat2 - lat1) * p)/2 + numpy.cos(lat1 * p) * \
55 a = 0.5 - numpy.cos((lat2 - lat1) * p)/2 + numpy.cos(lat1 * p) * \
56 numpy.cos(lat2 * p) * (1 - numpy.cos((lon2 - lon1) * p)) / 2
56 numpy.cos(lat2 * p) * (1 - numpy.cos((lon2 - lon1) * p)) / 2
57 r = 12742 * numpy.arcsin(numpy.sqrt(a))
57 r = 12742 * numpy.arcsin(numpy.sqrt(a))
58 theta = numpy.arctan2(numpy.sin((lon2-lon1)*p)*numpy.cos(lat2*p), numpy.cos(lat1*p)
58 theta = numpy.arctan2(numpy.sin((lon2-lon1)*p)*numpy.cos(lat2*p), numpy.cos(lat1*p)
59 * numpy.sin(lat2*p)-numpy.sin(lat1*p)*numpy.cos(lat2*p)*numpy.cos((lon2-lon1)*p))
59 * numpy.sin(lat2*p)-numpy.sin(lat1*p)*numpy.cos(lat2*p)*numpy.cos((lon2-lon1)*p))
60 theta = -theta + numpy.pi/2
60 theta = -theta + numpy.pi/2
61 return r*numpy.cos(theta), r*numpy.sin(theta)
61 return r*numpy.cos(theta), r*numpy.sin(theta)
62
62
63
63
64 def km2deg(km):
64 def km2deg(km):
65 '''
65 '''
66 Convert distance in km to degrees
66 Convert distance in km to degrees
67 '''
67 '''
68
68
69 return numpy.rad2deg(km/EARTH_RADIUS)
69 return numpy.rad2deg(km/EARTH_RADIUS)
70
70
71
71
72 def figpause(interval):
72 def figpause(interval):
73 backend = plt.rcParams['backend']
73 backend = plt.rcParams['backend']
74 if backend in matplotlib.rcsetup.interactive_bk:
74 if backend in matplotlib.rcsetup.interactive_bk:
75 figManager = matplotlib._pylab_helpers.Gcf.get_active()
75 figManager = matplotlib._pylab_helpers.Gcf.get_active()
76 if figManager is not None:
76 if figManager is not None:
77 canvas = figManager.canvas
77 canvas = figManager.canvas
78 if canvas.figure.stale:
78 if canvas.figure.stale:
79 canvas.draw()
79 canvas.draw()
80 try:
80 try:
81 canvas.start_event_loop(interval)
81 canvas.start_event_loop(interval)
82 except:
82 except:
83 pass
83 pass
84 return
84 return
85
85
86 def popup(message):
86 def popup(message):
87 '''
87 '''
88 '''
88 '''
89
89
90 fig = plt.figure(figsize=(12, 8), facecolor='r')
90 fig = plt.figure(figsize=(12, 8), facecolor='r')
91 text = '\n'.join([s.strip() for s in message.split(':')])
91 text = '\n'.join([s.strip() for s in message.split(':')])
92 fig.text(0.01, 0.5, text, ha='left', va='center',
92 fig.text(0.01, 0.5, text, ha='left', va='center',
93 size='20', weight='heavy', color='w')
93 size='20', weight='heavy', color='w')
94 fig.show()
94 fig.show()
95 figpause(1000)
95 figpause(1000)
96
96
97
97
98 class Throttle(object):
98 class Throttle(object):
99 '''
99 '''
100 Decorator that prevents a function from being called more than once every
100 Decorator that prevents a function from being called more than once every
101 time period.
101 time period.
102 To create a function that cannot be called more than once a minute, but
102 To create a function that cannot be called more than once a minute, but
103 will sleep until it can be called:
103 will sleep until it can be called:
104 @Throttle(minutes=1)
104 @Throttle(minutes=1)
105 def foo():
105 def foo():
106 pass
106 pass
107
107
108 for i in range(10):
108 for i in range(10):
109 foo()
109 foo()
110 print "This function has run %s times." % i
110 print "This function has run %s times." % i
111 '''
111 '''
112
112
113 def __init__(self, seconds=0, minutes=0, hours=0):
113 def __init__(self, seconds=0, minutes=0, hours=0):
114 self.throttle_period = datetime.timedelta(
114 self.throttle_period = datetime.timedelta(
115 seconds=seconds, minutes=minutes, hours=hours
115 seconds=seconds, minutes=minutes, hours=hours
116 )
116 )
117
117
118 self.time_of_last_call = datetime.datetime.min
118 self.time_of_last_call = datetime.datetime.min
119
119
120 def __call__(self, fn):
120 def __call__(self, fn):
121 @wraps(fn)
121 @wraps(fn)
122 def wrapper(*args, **kwargs):
122 def wrapper(*args, **kwargs):
123 coerce = kwargs.pop('coerce', None)
123 coerce = kwargs.pop('coerce', None)
124 if coerce:
124 if coerce:
125 self.time_of_last_call = datetime.datetime.now()
125 self.time_of_last_call = datetime.datetime.now()
126 return fn(*args, **kwargs)
126 return fn(*args, **kwargs)
127 else:
127 else:
128 now = datetime.datetime.now()
128 now = datetime.datetime.now()
129 time_since_last_call = now - self.time_of_last_call
129 time_since_last_call = now - self.time_of_last_call
130 time_left = self.throttle_period - time_since_last_call
130 time_left = self.throttle_period - time_since_last_call
131
131
132 if time_left > datetime.timedelta(seconds=0):
132 if time_left > datetime.timedelta(seconds=0):
133 return
133 return
134
134
135 self.time_of_last_call = datetime.datetime.now()
135 self.time_of_last_call = datetime.datetime.now()
136 return fn(*args, **kwargs)
136 return fn(*args, **kwargs)
137
137
138 return wrapper
138 return wrapper
139
139
140 def apply_throttle(value):
140 def apply_throttle(value):
141
141
142 @Throttle(seconds=value)
142 @Throttle(seconds=value)
143 def fnThrottled(fn):
143 def fnThrottled(fn):
144 fn()
144 fn()
145
145
146 return fnThrottled
146 return fnThrottled
147
147
148
148
149 @MPDecorator
149 @MPDecorator
150 class Plot(Operation):
150 class Plot(Operation):
151 """Base class for Schain plotting operations
151 """Base class for Schain plotting operations
152
152
153 This class should never be use directtly you must subclass a new operation,
153 This class should never be use directtly you must subclass a new operation,
154 children classes must be defined as follow:
154 children classes must be defined as follow:
155
155
156 ExamplePlot(Plot):
156 ExamplePlot(Plot):
157
157
158 CODE = 'code'
158 CODE = 'code'
159 colormap = 'jet'
159 colormap = 'jet'
160 plot_type = 'pcolor' # options are ('pcolor', 'pcolorbuffer', 'scatter', 'scatterbuffer')
160 plot_type = 'pcolor' # options are ('pcolor', 'pcolorbuffer', 'scatter', 'scatterbuffer')
161
161
162 def setup(self):
162 def setup(self):
163 pass
163 pass
164
164
165 def plot(self):
165 def plot(self):
166 pass
166 pass
167
167
168 """
168 """
169
169
170 CODE = 'Figure'
170 CODE = 'Figure'
171 colormap = 'jet'
171 colormap = 'jet'
172 bgcolor = 'white'
172 bgcolor = 'white'
173 buffering = True
173 buffering = True
174 __missing = 1E30
174 __missing = 1E30
175
175
176 __attrs__ = ['show', 'save', 'ymin', 'ymax', 'zmin', 'zmax', 'title',
176 __attrs__ = ['show', 'save', 'ymin', 'ymax', 'zmin', 'zmax', 'title',
177 'showprofile']
177 'showprofile']
178
178
179 def __init__(self):
179 def __init__(self):
180
180
181 Operation.__init__(self)
181 Operation.__init__(self)
182 self.isConfig = False
182 self.isConfig = False
183 self.isPlotConfig = False
183 self.isPlotConfig = False
184 self.save_time = 0
184 self.save_time = 0
185 self.sender_time = 0
185 self.sender_time = 0
186 self.data = None
186 self.data = None
187 self.firsttime = True
187 self.firsttime = True
188 self.sender_queue = deque(maxlen=10)
188 self.sender_queue = deque(maxlen=10)
189 self.plots_adjust = {'left': 0.125, 'right': 0.9, 'bottom': 0.15, 'top': 0.9, 'wspace': 0.2, 'hspace': 0.2}
189 self.plots_adjust = {'left': 0.125, 'right': 0.9, 'bottom': 0.15, 'top': 0.9, 'wspace': 0.2, 'hspace': 0.2}
190
190
191 def __fmtTime(self, x, pos):
191 def __fmtTime(self, x, pos):
192 '''
192 '''
193 '''
193 '''
194 if self.t_units == "h_m":
194 if self.t_units == "h_m":
195 return '{}'.format(self.getDateTime(x).strftime('%H:%M'))
195 return '{}'.format(self.getDateTime(x).strftime('%H:%M'))
196 if self.t_units == "h":
196 if self.t_units == "h":
197 return '{}'.format(self.getDateTime(x).strftime('%H'))
197 return '{}'.format(self.getDateTime(x).strftime('%H'))
198
198
199 def __setup(self, **kwargs):
199 def __setup(self, **kwargs):
200 '''
200 '''
201 Initialize variables
201 Initialize variables
202 '''
202 '''
203
203
204 self.figures = []
204 self.figures = []
205 self.axes = []
205 self.axes = []
206 self.cb_axes = []
206 self.cb_axes = []
207 self.pf_axes = []
207 self.pf_axes = []
208 self.localtime = kwargs.pop('localtime', True)
208 self.localtime = kwargs.pop('localtime', True)
209 self.show = kwargs.get('show', True)
209 self.show = kwargs.get('show', True)
210 self.save = kwargs.get('save', False)
210 self.save = kwargs.get('save', False)
211 self.save_period = kwargs.get('save_period', 0)
211 self.save_period = kwargs.get('save_period', 0)
212 self.colormap = kwargs.get('colormap', self.colormap)
212 self.colormap = kwargs.get('colormap', self.colormap)
213 self.colormap_coh = kwargs.get('colormap_coh', 'jet')
213 self.colormap_coh = kwargs.get('colormap_coh', 'jet')
214 self.colormap_phase = kwargs.get('colormap_phase', 'RdBu_r')
214 self.colormap_phase = kwargs.get('colormap_phase', 'RdBu_r')
215 self.colormaps = kwargs.get('colormaps', None)
215 self.colormaps = kwargs.get('colormaps', None)
216 self.bgcolor = kwargs.get('bgcolor', self.bgcolor)
216 self.bgcolor = kwargs.get('bgcolor', self.bgcolor)
217 self.showprofile = kwargs.get('showprofile', False)
217 self.showprofile = kwargs.get('showprofile', False)
218 self.title = kwargs.get('wintitle', self.CODE.upper())
218 self.title = kwargs.get('wintitle', self.CODE.upper())
219 self.cb_label = kwargs.get('cb_label', None)
219 self.cb_label = kwargs.get('cb_label', None)
220 self.cb_labels = kwargs.get('cb_labels', None)
220 self.cb_labels = kwargs.get('cb_labels', None)
221 self.labels = kwargs.get('labels', None)
221 self.labels = kwargs.get('labels', None)
222 self.xaxis = kwargs.get('xaxis', 'frequency')
222 self.xaxis = kwargs.get('xaxis', 'frequency')
223 self.zmin = kwargs.get('zmin', None)
223 self.zmin = kwargs.get('zmin', None)
224 self.zmax = kwargs.get('zmax', None)
224 self.zmax = kwargs.get('zmax', None)
225 self.zlimits = kwargs.get('zlimits', None)
225 self.zlimits = kwargs.get('zlimits', None)
226 self.xmin = kwargs.get('xmin', None)
226 self.xmin = kwargs.get('xmin', None)
227 self.xmax = kwargs.get('xmax', None)
227 self.xmax = kwargs.get('xmax', None)
228 self.xrange = kwargs.get('xrange', 12)
228 self.xrange = kwargs.get('xrange', 12)
229 self.xscale = kwargs.get('xscale', None)
229 self.xscale = kwargs.get('xscale', None)
230 self.ymin = kwargs.get('ymin', None)
230 self.ymin = kwargs.get('ymin', None)
231 self.ymax = kwargs.get('ymax', None)
231 self.ymax = kwargs.get('ymax', None)
232 self.yscale = kwargs.get('yscale', None)
232 self.yscale = kwargs.get('yscale', None)
233 self.xlabel = kwargs.get('xlabel', None)
233 self.xlabel = kwargs.get('xlabel', None)
234 self.attr_time = kwargs.get('attr_time', 'utctime')
234 self.attr_time = kwargs.get('attr_time', 'utctime')
235 self.attr_data = kwargs.get('attr_data', 'data_param')
235 self.attr_data = kwargs.get('attr_data', 'data_param')
236 self.decimation = kwargs.get('decimation', None)
236 self.decimation = kwargs.get('decimation', None)
237 self.oneFigure = kwargs.get('oneFigure', True)
237 self.oneFigure = kwargs.get('oneFigure', True)
238 self.width = kwargs.get('width', None)
238 self.width = kwargs.get('width', None)
239 self.height = kwargs.get('height', None)
239 self.height = kwargs.get('height', None)
240 self.colorbar = kwargs.get('colorbar', True)
240 self.colorbar = kwargs.get('colorbar', True)
241 self.factors = kwargs.get('factors', range(18))
241 self.factors = kwargs.get('factors', range(18))
242 self.channels = kwargs.get('channels', None)
242 self.channels = kwargs.get('channels', None)
243 self.titles = kwargs.get('titles', [])
243 self.titles = kwargs.get('titles', [])
244 self.polar = False
244 self.polar = False
245 self.type = kwargs.get('type', 'iq')
245 self.type = kwargs.get('type', 'iq')
246 self.grid = kwargs.get('grid', False)
246 self.grid = kwargs.get('grid', False)
247 self.pause = kwargs.get('pause', False)
247 self.pause = kwargs.get('pause', False)
248 self.save_code = kwargs.get('save_code', self.CODE)
248 self.save_code = kwargs.get('save_code', self.CODE)
249 self.throttle = kwargs.get('throttle', 0)
249 self.throttle = kwargs.get('throttle', 0)
250 self.exp_code = kwargs.get('exp_code', None)
250 self.exp_code = kwargs.get('exp_code', None)
251 self.server = kwargs.get('server', False)
251 self.server = kwargs.get('server', False)
252 self.sender_period = kwargs.get('sender_period', 60)
252 self.sender_period = kwargs.get('sender_period', 60)
253 self.tag = kwargs.get('tag', '')
253 self.tag = kwargs.get('tag', '')
254 self.height_index = kwargs.get('height_index', [])
254 self.height_index = kwargs.get('height_index', [])
255 self.__throttle_plot = apply_throttle(self.throttle)
255 self.__throttle_plot = apply_throttle(self.throttle)
256 code = self.attr_data if self.attr_data else self.CODE
256 code = self.attr_data if self.attr_data else self.CODE
257 self.data = PlotterData(self.CODE, self.exp_code, self.localtime)
257 self.data = PlotterData(self.CODE, self.exp_code, self.localtime)
258 self.tmin = kwargs.get('tmin', None)
258 self.tmin = kwargs.get('tmin', None)
259 self.t_units = kwargs.get('t_units', "h_m")
259 self.t_units = kwargs.get('t_units', "h_m")
260 self.selectedHeightsList = kwargs.get('selectedHeightsList', [])
260 self.selectedHeightsList = kwargs.get('selectedHeightsList', [])
261 if isinstance(self.selectedHeightsList, int):
261 if isinstance(self.selectedHeightsList, int):
262 self.selectedHeightsList = [self.selectedHeightsList]
262 self.selectedHeightsList = [self.selectedHeightsList]
263
263
264 if self.server:
264 if self.server:
265 if not self.server.startswith('tcp://'):
265 if not self.server.startswith('tcp://'):
266 self.server = 'tcp://{}'.format(self.server)
266 self.server = 'tcp://{}'.format(self.server)
267 log.success(
267 log.success(
268 'Sending to server: {}'.format(self.server),
268 'Sending to server: {}'.format(self.server),
269 self.name
269 self.name
270 )
270 )
271
271
272 if isinstance(self.attr_data, str):
272 if isinstance(self.attr_data, str):
273 self.attr_data = [self.attr_data]
273 self.attr_data = [self.attr_data]
274
274
275 def __setup_plot(self):
275 def __setup_plot(self):
276 '''
276 '''
277 Common setup for all figures, here figures and axes are created
277 Common setup for all figures, here figures and axes are created
278 '''
278 '''
279
279
280 self.setup()
280 self.setup()
281
281
282 self.time_label = 'LT' if self.localtime else 'UTC'
282 self.time_label = 'LT' if self.localtime else 'UTC'
283
283
284 if self.width is None:
284 if self.width is None:
285 self.width = 8
285 self.width = 8
286
286
287 self.figures = []
287 self.figures = []
288 self.axes = []
288 self.axes = []
289 self.cb_axes = []
289 self.cb_axes = []
290 self.pf_axes = []
290 self.pf_axes = []
291 self.cmaps = []
291 self.cmaps = []
292
292
293 size = '15%' if self.ncols == 1 else '30%'
293 size = '15%' if self.ncols == 1 else '30%'
294 pad = '4%' if self.ncols == 1 else '8%'
294 pad = '4%' if self.ncols == 1 else '8%'
295
295
296 if self.oneFigure:
296 if self.oneFigure:
297 if self.height is None:
297 if self.height is None:
298 self.height = 1.4 * self.nrows + 1
298 self.height = 1.4 * self.nrows + 1
299 fig = plt.figure(figsize=(self.width, self.height),
299 fig = plt.figure(figsize=(self.width, self.height),
300 edgecolor='k',
300 edgecolor='k',
301 facecolor='w')
301 facecolor='w')
302 self.figures.append(fig)
302 self.figures.append(fig)
303 for n in range(self.nplots):
303 for n in range(self.nplots):
304 ax = fig.add_subplot(self.nrows, self.ncols,
304 ax = fig.add_subplot(self.nrows, self.ncols,
305 n + 1, polar=self.polar)
305 n + 1, polar=self.polar)
306 ax.tick_params(labelsize=8)
306 ax.tick_params(labelsize=8)
307 ax.firsttime = True
307 ax.firsttime = True
308 ax.index = 0
308 ax.index = 0
309 ax.press = None
309 ax.press = None
310 self.axes.append(ax)
310 self.axes.append(ax)
311 if self.showprofile:
311 if self.showprofile:
312 cax = self.__add_axes(ax, size=size, pad=pad)
312 cax = self.__add_axes(ax, size=size, pad=pad)
313 cax.tick_params(labelsize=8)
313 cax.tick_params(labelsize=8)
314 self.pf_axes.append(cax)
314 self.pf_axes.append(cax)
315 else:
315 else:
316 if self.height is None:
316 if self.height is None:
317 self.height = 3
317 self.height = 3
318 for n in range(self.nplots):
318 for n in range(self.nplots):
319 fig = plt.figure(figsize=(self.width, self.height),
319 fig = plt.figure(figsize=(self.width, self.height),
320 edgecolor='k',
320 edgecolor='k',
321 facecolor='w')
321 facecolor='w')
322 ax = fig.add_subplot(1, 1, 1, polar=self.polar)
322 ax = fig.add_subplot(1, 1, 1, polar=self.polar)
323 ax.tick_params(labelsize=8)
323 ax.tick_params(labelsize=8)
324 ax.firsttime = True
324 ax.firsttime = True
325 ax.index = 0
325 ax.index = 0
326 ax.press = None
326 ax.press = None
327 self.figures.append(fig)
327 self.figures.append(fig)
328 self.axes.append(ax)
328 self.axes.append(ax)
329 if self.showprofile:
329 if self.showprofile:
330 cax = self.__add_axes(ax, size=size, pad=pad)
330 cax = self.__add_axes(ax, size=size, pad=pad)
331 cax.tick_params(labelsize=8)
331 cax.tick_params(labelsize=8)
332 self.pf_axes.append(cax)
332 self.pf_axes.append(cax)
333
333
334 for n in range(self.nrows):
334 for n in range(self.nrows):
335 if self.colormaps is not None:
335 if self.colormaps is not None:
336 cmap = plt.get_cmap(self.colormaps[n])
336 cmap = plt.get_cmap(self.colormaps[n])
337 else:
337 else:
338 cmap = plt.get_cmap(self.colormap)
338 cmap = plt.get_cmap(self.colormap)
339 cmap.set_bad(self.bgcolor, 1.)
339 cmap.set_bad(self.bgcolor, 1.)
340 self.cmaps.append(cmap)
340 self.cmaps.append(cmap)
341
341
342 def __add_axes(self, ax, size='30%', pad='8%'):
342 def __add_axes(self, ax, size='30%', pad='8%'):
343 '''
343 '''
344 Add new axes to the given figure
344 Add new axes to the given figure
345 '''
345 '''
346 divider = make_axes_locatable(ax)
346 divider = make_axes_locatable(ax)
347 nax = divider.new_horizontal(size=size, pad=pad)
347 nax = divider.new_horizontal(size=size, pad=pad)
348 ax.figure.add_axes(nax)
348 ax.figure.add_axes(nax)
349 return nax
349 return nax
350
350
351 def fill_gaps(self, x_buffer, y_buffer, z_buffer):
351 def fill_gaps(self, x_buffer, y_buffer, z_buffer):
352 '''
352 '''
353 Create a masked array for missing data
353 Create a masked array for missing data
354 '''
354 '''
355 if x_buffer.shape[0] < 2:
355 if x_buffer.shape[0] < 2:
356 return x_buffer, y_buffer, z_buffer
356 return x_buffer, y_buffer, z_buffer
357
357
358 deltas = x_buffer[1:] - x_buffer[0:-1]
358 deltas = x_buffer[1:] - x_buffer[0:-1]
359 x_median = numpy.median(deltas)
359 x_median = numpy.median(deltas)
360
360
361 index = numpy.where(deltas > 5 * x_median)
361 index = numpy.where(deltas > 5 * x_median)
362
362
363 if len(index[0]) != 0:
363 if len(index[0]) != 0:
364 z_buffer[::, index[0], ::] = self.__missing
364 z_buffer[::, index[0], ::] = self.__missing
365 z_buffer = numpy.ma.masked_inside(z_buffer,
365 z_buffer = numpy.ma.masked_inside(z_buffer,
366 0.99 * self.__missing,
366 0.99 * self.__missing,
367 1.01 * self.__missing)
367 1.01 * self.__missing)
368
368
369 return x_buffer, y_buffer, z_buffer
369 return x_buffer, y_buffer, z_buffer
370
370
371 def decimate(self):
371 def decimate(self):
372
372
373 # dx = int(len(self.x)/self.__MAXNUMX) + 1
373 # dx = int(len(self.x)/self.__MAXNUMX) + 1
374 dy = int(len(self.y) / self.decimation) + 1
374 dy = int(len(self.y) / self.decimation) + 1
375
375
376 # x = self.x[::dx]
376 # x = self.x[::dx]
377 x = self.x
377 x = self.x
378 y = self.y[::dy]
378 y = self.y[::dy]
379 z = self.z[::, ::, ::dy]
379 z = self.z[::, ::, ::dy]
380
380
381 return x, y, z
381 return x, y, z
382
382
383 def format(self):
383 def format(self):
384 '''
384 '''
385 Set min and max values, labels, ticks and titles
385 Set min and max values, labels, ticks and titles
386 '''
386 '''
387
387
388 for n, ax in enumerate(self.axes):
388 for n, ax in enumerate(self.axes):
389 if ax.firsttime:
389 if ax.firsttime:
390 if self.xaxis != 'time':
390 if self.xaxis != 'time':
391 xmin = self.xmin
391 xmin = self.xmin
392 xmax = self.xmax
392 xmax = self.xmax
393 else:
393 else:
394 xmin = self.tmin
394 xmin = self.tmin
395 xmax = self.tmin + self.xrange*60*60
395 xmax = self.tmin + self.xrange*60*60
396 ax.xaxis.set_major_formatter(FuncFormatter(self.__fmtTime))
396 ax.xaxis.set_major_formatter(FuncFormatter(self.__fmtTime))
397 if self.t_units == "h_m":
397 if self.t_units == "h_m":
398 ax.xaxis.set_major_locator(LinearLocator(9))
398 ax.xaxis.set_major_locator(LinearLocator(9))
399 if self.t_units == "h":
399 if self.t_units == "h":
400 ax.xaxis.set_major_locator(LinearLocator(int((xmax-xmin)/3600)+1))
400 ax.xaxis.set_major_locator(LinearLocator(int((xmax-xmin)/3600)+1))
401 ymin = self.ymin if self.ymin is not None else numpy.nanmin(self.y[numpy.isfinite(self.y)])
401 ymin = self.ymin if self.ymin is not None else numpy.nanmin(self.y[numpy.isfinite(self.y)])
402 ymax = self.ymax if self.ymax is not None else numpy.nanmax(self.y[numpy.isfinite(self.y)])
402 ymax = self.ymax if self.ymax is not None else numpy.nanmax(self.y[numpy.isfinite(self.y)])
403 ax.set_facecolor(self.bgcolor)
403 ax.set_facecolor(self.bgcolor)
404 if self.xscale:
404 if self.xscale:
405 ax.xaxis.set_major_formatter(FuncFormatter(
405 ax.xaxis.set_major_formatter(FuncFormatter(
406 lambda x, pos: '{0:g}'.format(x*self.xscale)))
406 lambda x, pos: '{0:g}'.format(x*self.xscale)))
407 if self.yscale:
407 if self.yscale:
408 ax.yaxis.set_major_formatter(FuncFormatter(
408 ax.yaxis.set_major_formatter(FuncFormatter(
409 lambda x, pos: '{0:g}'.format(x*self.yscale)))
409 lambda x, pos: '{0:g}'.format(x*self.yscale)))
410 if self.xlabel is not None:
410 if self.xlabel is not None:
411 ax.set_xlabel(self.xlabel)
411 ax.set_xlabel(self.xlabel)
412 if self.ylabel is not None:
412 if self.ylabel is not None:
413 ax.set_ylabel(self.ylabel)
413 ax.set_ylabel(self.ylabel)
414 if self.showprofile:
414 if self.showprofile:
415 self.pf_axes[n].set_ylim(ymin, ymax)
415 self.pf_axes[n].set_ylim(ymin, ymax)
416 self.pf_axes[n].set_xlim(self.zmin, self.zmax)
416 self.pf_axes[n].set_xlim(self.zmin, self.zmax)
417 self.pf_axes[n].set_xlabel('dB')
417 self.pf_axes[n].set_xlabel('dB')
418 self.pf_axes[n].grid(b=True, axis='x')
418 self.pf_axes[n].grid(b=True, axis='x')
419 [tick.set_visible(False)
419 [tick.set_visible(False)
420 for tick in self.pf_axes[n].get_yticklabels()]
420 for tick in self.pf_axes[n].get_yticklabels()]
421 if self.colorbar:
421 if self.colorbar:
422 ax.cbar = plt.colorbar(
422 ax.cbar = plt.colorbar(
423 ax.plt, ax=ax, fraction=0.05, pad=0.02, aspect=10)
423 ax.plt, ax=ax, fraction=0.05, pad=0.02, aspect=10)
424 ax.cbar.ax.tick_params(labelsize=8)
424 ax.cbar.ax.tick_params(labelsize=8)
425 ax.cbar.ax.press = None
425 ax.cbar.ax.press = None
426 if self.cb_label:
426 if self.cb_label:
427 ax.cbar.set_label(self.cb_label, size=8)
427 ax.cbar.set_label(self.cb_label, size=8)
428 elif self.cb_labels:
428 elif self.cb_labels:
429 ax.cbar.set_label(self.cb_labels[n], size=8)
429 ax.cbar.set_label(self.cb_labels[n], size=8)
430 else:
430 else:
431 ax.cbar = None
431 ax.cbar = None
432 ax.set_xlim(xmin, xmax)
432 ax.set_xlim(xmin, xmax)
433 ax.set_ylim(ymin, ymax)
433 ax.set_ylim(ymin, ymax)
434 ax.firsttime = False
434 ax.firsttime = False
435 if self.grid:
435 if self.grid:
436 ax.grid(True)
436 ax.grid(True)
437
437
438 if not self.polar:
438 if not self.polar:
439 ax.set_title('{} {} {}'.format(
439 ax.set_title('{} {} {}'.format(
440 self.titles[n],
440 self.titles[n],
441 self.getDateTime(self.data.max_time).strftime(
441 self.getDateTime(self.data.max_time).strftime(
442 '%Y-%m-%d %H:%M:%S'),
442 '%Y-%m-%d %H:%M:%S'),
443 self.time_label),
443 self.time_label),
444 size=8)
444 size=8)
445 else:
445 else:
446
446
447 ax.set_title('{}'.format(self.titles[n]), size=8)
447 ax.set_title('{}'.format(self.titles[n]), size=8)
448 ax.set_ylim(0, 90)
448 ax.set_ylim(0, 90)
449 ax.set_yticks(numpy.arange(0, 90, 20))
449 ax.set_yticks(numpy.arange(0, 90, 20))
450 ax.yaxis.labelpad = 40
450 ax.yaxis.labelpad = 40
451
451
452 if self.firsttime:
452 if self.firsttime:
453 for n, fig in enumerate(self.figures):
453 for n, fig in enumerate(self.figures):
454 fig.subplots_adjust(**self.plots_adjust)
454 fig.subplots_adjust(**self.plots_adjust)
455 self.firsttime = False
455 self.firsttime = False
456
456
457 def clear_figures(self):
457 def clear_figures(self):
458 '''
458 '''
459 Reset axes for redraw plots
459 Reset axes for redraw plots
460 '''
460 '''
461
461
462 for ax in self.axes+self.pf_axes+self.cb_axes:
462 for ax in self.axes+self.pf_axes+self.cb_axes:
463 ax.clear()
463 ax.clear()
464 ax.firsttime = True
464 ax.firsttime = True
465 if hasattr(ax, 'cbar') and ax.cbar:
465 if hasattr(ax, 'cbar') and ax.cbar:
466 ax.cbar.remove()
466 ax.cbar.remove()
467
467
468 def __plot(self):
468 def __plot(self):
469 '''
469 '''
470 Main function to plot, format and save figures
470 Main function to plot, format and save figures
471 '''
471 '''
472
472
473 self.plot()
473 self.plot()
474 self.format()
474 self.format()
475
475
476 for n, fig in enumerate(self.figures):
476 for n, fig in enumerate(self.figures):
477 if self.nrows == 0 or self.nplots == 0:
477 if self.nrows == 0 or self.nplots == 0:
478 log.warning('No data', self.name)
478 log.warning('No data', self.name)
479 fig.text(0.5, 0.5, 'No Data', fontsize='large', ha='center')
479 fig.text(0.5, 0.5, 'No Data', fontsize='large', ha='center')
480 fig.canvas.manager.set_window_title(self.CODE)
480 fig.canvas.manager.set_window_title(self.CODE)
481 continue
481 continue
482
482
483 fig.canvas.manager.set_window_title('{} - {}'.format(self.title,
483 fig.canvas.manager.set_window_title('{} - {}'.format(self.title,
484 self.getDateTime(self.data.max_time).strftime('%Y/%m/%d')))
484 self.getDateTime(self.data.max_time).strftime('%Y/%m/%d')))
485
485
486 fig.canvas.draw()
486 fig.canvas.draw()
487 if self.show:
487 if self.show:
488 fig.show()
488 fig.show()
489 figpause(0.01)
489 figpause(0.01)
490
490
491 if self.save:
491 if self.save:
492 self.save_figure(n)
492 self.save_figure(n)
493
493
494 if self.server:
494 if self.server:
495 self.send_to_server()
495 self.send_to_server()
496
496
497 def __update(self, dataOut, timestamp):
497 def __update(self, dataOut, timestamp):
498 '''
498 '''
499 '''
499 '''
500
500
501 metadata = {
501 metadata = {
502 'yrange': dataOut.heightList,
502 'yrange': dataOut.heightList,
503 'interval': dataOut.timeInterval,
503 'interval': dataOut.timeInterval,
504 'channels': dataOut.channelList
504 'channels': dataOut.channelList
505 }
505 }
506 data, meta = self.update(dataOut)
506 data, meta = self.update(dataOut)
507 metadata.update(meta)
507 metadata.update(meta)
508 self.data.update(data, timestamp, metadata)
508 self.data.update(data, timestamp, metadata)
509
509
510 def save_figure(self, n):
510 def save_figure(self, n):
511 '''
511 '''
512 '''
512 '''
513
513
514 if (self.data.max_time - self.save_time) <= self.save_period:
514 if (self.data.max_time - self.save_time) <= self.save_period:
515 return
515 return
516
516
517 self.save_time = self.data.max_time
517 self.save_time = self.data.max_time
518
518
519 fig = self.figures[n]
519 fig = self.figures[n]
520
520
521 if self.throttle == 0:
521 if self.throttle == 0:
522 figname = os.path.join(
522 figname = os.path.join(
523 self.save,
523 self.save,
524 self.save_code,
524 self.save_code,
525 '{}_{}.png'.format(
525 '{}_{}.jpeg'.format(
526 self.save_code,
526 self.save_code,
527 self.getDateTime(self.data.max_time).strftime(
527 self.getDateTime(self.data.max_time).strftime(
528 '%Y%m%d_%H%M%S'
528 '%Y%m%d_%H%M%S'
529 ),
529 ),
530 )
530 )
531 )
531 )
532 log.log('Saving figure: {}'.format(figname), self.name)
532 log.log('Saving figure: {}'.format(figname), self.name)
533 if not os.path.isdir(os.path.dirname(figname)):
533 if not os.path.isdir(os.path.dirname(figname)):
534 os.makedirs(os.path.dirname(figname))
534 os.makedirs(os.path.dirname(figname))
535 fig.savefig(figname)
535 fig.savefig(figname)
536
536
537 figname = os.path.join(
537 figname = os.path.join(
538 self.save,
538 self.save,
539 '{}_{}.png'.format(
539 '{}_{}.jpeg'.format(
540 self.save_code,
540 self.save_code,
541 self.getDateTime(self.data.min_time).strftime(
541 self.getDateTime(self.data.min_time).strftime(
542 '%Y%m%d'
542 '%Y%m%d'
543 ),
543 ),
544 )
544 )
545 )
545 )
546
546
547 log.log('Saving figure: {}'.format(figname), self.name)
547 log.log('Saving figure: {}'.format(figname), self.name)
548 if not os.path.isdir(os.path.dirname(figname)):
548 if not os.path.isdir(os.path.dirname(figname)):
549 os.makedirs(os.path.dirname(figname))
549 os.makedirs(os.path.dirname(figname))
550 fig.savefig(figname)
550 fig.savefig(figname)
551
551
552 def send_to_server(self):
552 def send_to_server(self):
553 '''
553 '''
554 '''
554 '''
555
555
556 if self.exp_code == None:
556 if self.exp_code == None:
557 log.warning('Missing `exp_code` skipping sending to server...')
557 log.warning('Missing `exp_code` skipping sending to server...')
558
558
559 last_time = self.data.max_time
559 last_time = self.data.max_time
560 interval = last_time - self.sender_time
560 interval = last_time - self.sender_time
561 if interval < self.sender_period:
561 if interval < self.sender_period:
562 return
562 return
563
563
564 self.sender_time = last_time
564 self.sender_time = last_time
565
565
566 attrs = ['titles', 'zmin', 'zmax', 'tag', 'ymin', 'ymax']
566 attrs = ['titles', 'zmin', 'zmax', 'tag', 'ymin', 'ymax']
567 for attr in attrs:
567 for attr in attrs:
568 value = getattr(self, attr)
568 value = getattr(self, attr)
569 if value:
569 if value:
570 if isinstance(value, (numpy.float32, numpy.float64)):
570 if isinstance(value, (numpy.float32, numpy.float64)):
571 value = round(float(value), 2)
571 value = round(float(value), 2)
572 self.data.meta[attr] = value
572 self.data.meta[attr] = value
573 if self.colormap == 'jet':
573 if self.colormap == 'jet':
574 self.data.meta['colormap'] = 'Jet'
574 self.data.meta['colormap'] = 'Jet'
575 elif 'RdBu' in self.colormap:
575 elif 'RdBu' in self.colormap:
576 self.data.meta['colormap'] = 'RdBu'
576 self.data.meta['colormap'] = 'RdBu'
577 else:
577 else:
578 self.data.meta['colormap'] = 'Viridis'
578 self.data.meta['colormap'] = 'Viridis'
579 self.data.meta['interval'] = int(interval)
579 self.data.meta['interval'] = int(interval)
580
580
581 self.sender_queue.append(last_time)
581 self.sender_queue.append(last_time)
582
582
583 while 1:
583 while 1:
584 try:
584 try:
585 tm = self.sender_queue.popleft()
585 tm = self.sender_queue.popleft()
586 except IndexError:
586 except IndexError:
587 break
587 break
588 msg = self.data.jsonify(tm, self.save_code, self.plot_type)
588 msg = self.data.jsonify(tm, self.save_code, self.plot_type)
589 self.socket.send_string(msg)
589 self.socket.send_string(msg)
590 socks = dict(self.poll.poll(2000))
590 socks = dict(self.poll.poll(2000))
591 if socks.get(self.socket) == zmq.POLLIN:
591 if socks.get(self.socket) == zmq.POLLIN:
592 reply = self.socket.recv_string()
592 reply = self.socket.recv_string()
593 if reply == 'ok':
593 if reply == 'ok':
594 log.log("Response from server ok", self.name)
594 log.log("Response from server ok", self.name)
595 time.sleep(0.1)
595 time.sleep(0.1)
596 continue
596 continue
597 else:
597 else:
598 log.warning(
598 log.warning(
599 "Malformed reply from server: {}".format(reply), self.name)
599 "Malformed reply from server: {}".format(reply), self.name)
600 else:
600 else:
601 log.warning(
601 log.warning(
602 "No response from server, retrying...", self.name)
602 "No response from server, retrying...", self.name)
603 self.sender_queue.appendleft(tm)
603 self.sender_queue.appendleft(tm)
604 self.socket.setsockopt(zmq.LINGER, 0)
604 self.socket.setsockopt(zmq.LINGER, 0)
605 self.socket.close()
605 self.socket.close()
606 self.poll.unregister(self.socket)
606 self.poll.unregister(self.socket)
607 self.socket = self.context.socket(zmq.REQ)
607 self.socket = self.context.socket(zmq.REQ)
608 self.socket.connect(self.server)
608 self.socket.connect(self.server)
609 self.poll.register(self.socket, zmq.POLLIN)
609 self.poll.register(self.socket, zmq.POLLIN)
610 break
610 break
611
611
612 def setup(self):
612 def setup(self):
613 '''
613 '''
614 This method should be implemented in the child class, the following
614 This method should be implemented in the child class, the following
615 attributes should be set:
615 attributes should be set:
616
616
617 self.nrows: number of rows
617 self.nrows: number of rows
618 self.ncols: number of cols
618 self.ncols: number of cols
619 self.nplots: number of plots (channels or pairs)
619 self.nplots: number of plots (channels or pairs)
620 self.ylabel: label for Y axes
620 self.ylabel: label for Y axes
621 self.titles: list of axes title
621 self.titles: list of axes title
622
622
623 '''
623 '''
624 raise NotImplementedError
624 raise NotImplementedError
625
625
626 def plot(self):
626 def plot(self):
627 '''
627 '''
628 Must be defined in the child class, the actual plotting method
628 Must be defined in the child class, the actual plotting method
629 '''
629 '''
630 raise NotImplementedError
630 raise NotImplementedError
631
631
632 def update(self, dataOut):
632 def update(self, dataOut):
633 '''
633 '''
634 Must be defined in the child class, update self.data with new data
634 Must be defined in the child class, update self.data with new data
635 '''
635 '''
636
636
637 data = {
637 data = {
638 self.CODE: getattr(dataOut, 'data_{}'.format(self.CODE))
638 self.CODE: getattr(dataOut, 'data_{}'.format(self.CODE))
639 }
639 }
640 meta = {}
640 meta = {}
641
641
642 return data, meta
642 return data, meta
643
643
644 def run(self, dataOut, **kwargs):
644 def run(self, dataOut, **kwargs):
645 '''
645 '''
646 Main plotting routine
646 Main plotting routine
647 '''
647 '''
648 if self.isConfig is False:
648 if self.isConfig is False:
649 self.__setup(**kwargs)
649 self.__setup(**kwargs)
650
650
651 if self.localtime:
651 if self.localtime:
652 self.getDateTime = datetime.datetime.fromtimestamp
652 self.getDateTime = datetime.datetime.fromtimestamp
653 else:
653 else:
654 self.getDateTime = datetime.datetime.utcfromtimestamp
654 self.getDateTime = datetime.datetime.utcfromtimestamp
655
655
656 self.data.setup()
656 self.data.setup()
657 self.isConfig = True
657 self.isConfig = True
658 if self.server:
658 if self.server:
659 self.context = zmq.Context()
659 self.context = zmq.Context()
660 self.socket = self.context.socket(zmq.REQ)
660 self.socket = self.context.socket(zmq.REQ)
661 self.socket.connect(self.server)
661 self.socket.connect(self.server)
662 self.poll = zmq.Poller()
662 self.poll = zmq.Poller()
663 self.poll.register(self.socket, zmq.POLLIN)
663 self.poll.register(self.socket, zmq.POLLIN)
664
664
665 tm = getattr(dataOut, self.attr_time)
665 tm = getattr(dataOut, self.attr_time)
666
666
667 if self.data and 'time' in self.xaxis and (tm - self.tmin) >= self.xrange*60*60:
667 if self.data and 'time' in self.xaxis and (tm - self.tmin) >= self.xrange*60*60:
668 self.save_time = tm
668 self.save_time = tm
669 self.__plot()
669 self.__plot()
670 self.tmin += self.xrange*60*60
670 self.tmin += self.xrange*60*60
671 self.data.setup()
671 self.data.setup()
672 self.clear_figures()
672 self.clear_figures()
673
673
674 self.__update(dataOut, tm)
674 self.__update(dataOut, tm)
675
675
676 if self.isPlotConfig is False:
676 if self.isPlotConfig is False:
677 self.__setup_plot()
677 self.__setup_plot()
678 self.isPlotConfig = True
678 self.isPlotConfig = True
679 if self.xaxis == 'time':
679 if self.xaxis == 'time':
680 dt = self.getDateTime(tm)
680 dt = self.getDateTime(tm)
681 if self.xmin is None:
681 if self.xmin is None:
682 self.tmin = tm
682 self.tmin = tm
683 self.xmin = dt.hour
683 self.xmin = dt.hour
684 minutes = (self.xmin-int(self.xmin)) * 60
684 minutes = (self.xmin-int(self.xmin)) * 60
685 seconds = (minutes - int(minutes)) * 60
685 seconds = (minutes - int(minutes)) * 60
686 self.tmin = (dt.replace(hour=int(self.xmin), minute=int(minutes), second=int(seconds)) -
686 self.tmin = (dt.replace(hour=int(self.xmin), minute=int(minutes), second=int(seconds)) -
687 datetime.datetime(1970, 1, 1)).total_seconds()
687 datetime.datetime(1970, 1, 1)).total_seconds()
688 if self.localtime:
688 if self.localtime:
689 self.tmin += time.timezone
689 self.tmin += time.timezone
690
690
691 if self.xmin is not None and self.xmax is not None:
691 if self.xmin is not None and self.xmax is not None:
692 self.xrange = self.xmax - self.xmin
692 self.xrange = self.xmax - self.xmin
693
693
694 if self.throttle == 0:
694 if self.throttle == 0:
695 self.__plot()
695 self.__plot()
696 else:
696 else:
697 self.__throttle_plot(self.__plot)#, coerce=coerce)
697 self.__throttle_plot(self.__plot)#, coerce=coerce)
698
698
699 def close(self):
699 def close(self):
700
700
701 if self.data and not self.data.flagNoData:
701 if self.data and not self.data.flagNoData:
702 self.save_time = 0
702 self.save_time = 0
703 self.__plot()
703 self.__plot()
704 if self.data and not self.data.flagNoData and self.pause:
704 if self.data and not self.data.flagNoData and self.pause:
705 figpause(10)
705 figpause(10)
@@ -1,671 +1,691
1 ''''
1 ''''
2 Created on Set 9, 2015
2 Created on Set 9, 2015
3
3
4 @author: roj-idl71 Karim Kuyeng
4 @author: roj-idl71 Karim Kuyeng
5
5
6 @update: 2021, Joab Apaza
6 @update: 2021, Joab Apaza
7 '''
7 '''
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import glob
11 import glob
12 import fnmatch
12 import fnmatch
13 import datetime
13 import datetime
14 import time
14 import time
15 import re
15 import re
16 import h5py
16 import h5py
17 import numpy
17 import numpy
18
18
19 try:
19 try:
20 from gevent import sleep
20 from gevent import sleep
21 except:
21 except:
22 from time import sleep
22 from time import sleep
23
23
24 from schainpy.model.data.jroheaderIO import RadarControllerHeader, SystemHeader
24 from schainpy.model.data.jroheaderIO import RadarControllerHeader, SystemHeader
25 from schainpy.model.data.jrodata import Voltage
25 from schainpy.model.data.jrodata import Voltage
26 from schainpy.model.proc.jroproc_base import ProcessingUnit, Operation, MPDecorator
26 from schainpy.model.proc.jroproc_base import ProcessingUnit, Operation, MPDecorator
27 from numpy import imag
27 from numpy import imag
28 from schainpy.utils import log
28 from schainpy.utils import log
29
29
30
30
31 class AMISRReader(ProcessingUnit):
31 class AMISRReader(ProcessingUnit):
32 '''
32 '''
33 classdocs
33 classdocs
34 '''
34 '''
35
35
36 def __init__(self):
36 def __init__(self):
37 '''
37 '''
38 Constructor
38 Constructor
39 '''
39 '''
40
40
41 ProcessingUnit.__init__(self)
41 ProcessingUnit.__init__(self)
42
42
43 self.set = None
43 self.set = None
44 self.subset = None
44 self.subset = None
45 self.extension_file = '.h5'
45 self.extension_file = '.h5'
46 self.dtc_str = 'dtc'
46 self.dtc_str = 'dtc'
47 self.dtc_id = 0
47 self.dtc_id = 0
48 self.status = True
48 self.status = True
49 self.isConfig = False
49 self.isConfig = False
50 self.dirnameList = []
50 self.dirnameList = []
51 self.filenameList = []
51 self.filenameList = []
52 self.fileIndex = None
52 self.fileIndex = None
53 self.flagNoMoreFiles = False
53 self.flagNoMoreFiles = False
54 self.flagIsNewFile = 0
54 self.flagIsNewFile = 0
55 self.filename = ''
55 self.filename = ''
56 self.amisrFilePointer = None
56 self.amisrFilePointer = None
57 self.realBeamCode = []
57
58 self.beamCodeMap = None
58 self.beamCodeMap = None
59 self.azimuthList = []
59 self.azimuthList = []
60 self.elevationList = []
60 self.elevationList = []
61 self.dataShape = None
61 self.dataShape = None
62 self.flag_old_beams = False
62 self.flag_old_beams = False
63
63
64
64
65 self.profileIndex = 0
65 self.profileIndex = 0
66
66
67
67
68 self.beamCodeByFrame = None
68 self.beamCodeByFrame = None
69 self.radacTimeByFrame = None
69 self.radacTimeByFrame = None
70
70
71 self.dataset = None
71 self.dataset = None
72
72
73 self.__firstFile = True
73 self.__firstFile = True
74
74
75 self.buffer = None
75 self.buffer = None
76
76
77 self.timezone = 'ut'
77 self.timezone = 'ut'
78
78
79 self.__waitForNewFile = 20
79 self.__waitForNewFile = 20
80 self.__filename_online = None
80 self.__filename_online = None
81 #Is really necessary create the output object in the initializer
81 #Is really necessary create the output object in the initializer
82 self.dataOut = Voltage()
82 self.dataOut = Voltage()
83 self.dataOut.error=False
83 self.dataOut.error=False
84 self.margin_days = 1
84 self.margin_days = 1
85
85
86 def setup(self,path=None,
86 def setup(self,path=None,
87 startDate=None,
87 startDate=None,
88 endDate=None,
88 endDate=None,
89 startTime=None,
89 startTime=None,
90 endTime=None,
90 endTime=None,
91 walk=True,
91 walk=True,
92 timezone='ut',
92 timezone='ut',
93 all=0,
93 all=0,
94 code = None,
94 code = None,
95 nCode = 1,
95 nCode = 1,
96 nBaud = 0,
96 nBaud = 0,
97 online=False,
97 online=False,
98 old_beams=False,
98 old_beams=False,
99 margin_days=1):
99 margin_days=1,
100 nFFT = 1,
101 nChannels = None,
102 ):
100
103
101
104
102
105
103 self.timezone = timezone
106 self.timezone = timezone
104 self.all = all
107 self.all = all
105 self.online = online
108 self.online = online
106 self.flag_old_beams = old_beams
109 self.flag_old_beams = old_beams
107 self.code = code
110 self.code = code
108 self.nCode = int(nCode)
111 self.nCode = int(nCode)
109 self.nBaud = int(nBaud)
112 self.nBaud = int(nBaud)
110 self.margin_days = margin_days
113 self.margin_days = margin_days
111 self.__sampleRate = None
114 self.__sampleRate = None
112
115
116 self.nFFT = nFFT
117 self.nChannels = nChannels
118
113 #self.findFiles()
119 #self.findFiles()
114 if not(online):
120 if not(online):
115 #Busqueda de archivos offline
121 #Busqueda de archivos offline
116 self.searchFilesOffLine(path, startDate, endDate, startTime, endTime, walk)
122 self.searchFilesOffLine(path, startDate, endDate, startTime, endTime, walk)
117 else:
123 else:
118 self.searchFilesOnLine(path, startDate, endDate, startTime,endTime,walk)
124 self.searchFilesOnLine(path, startDate, endDate, startTime,endTime,walk)
119
125
120 if not(self.filenameList):
126 if not(self.filenameList):
121 raise schainpy.admin.SchainWarning("There is no files into the folder: %s"%(path))
127 raise schainpy.admin.SchainWarning("There is no files into the folder: %s"%(path))
122 #sys.exit(0)
128 #sys.exit(0)
123 self.dataOut.error = True
129 self.dataOut.error = True
124
130
125 self.fileIndex = 0
131 self.fileIndex = 0
126
132
127 self.readNextFile(online)
133 self.readNextFile(online)
128
134
129 '''
135 '''
130 Add code
136 Add code
131 '''
137 '''
132 self.isConfig = True
138 self.isConfig = True
133 # print("Setup Done")
139 # print("Setup Done")
134 pass
140 pass
135
141
136
142
137 def readAMISRHeader(self,fp):
143 def readAMISRHeader(self,fp):
138
144
139 if self.isConfig and (not self.flagNoMoreFiles):
145 if self.isConfig and (not self.flagNoMoreFiles):
140 newShape = fp.get('Raw11/Data/Samples/Data').shape[1:]
146 newShape = fp.get('Raw11/Data/Samples/Data').shape[1:]
141 if self.dataShape != newShape and newShape != None:
147 if self.dataShape != newShape and newShape != None:
142 raise schainpy.admin.SchainError("NEW FILE HAS A DIFFERENT SHAPE: ")
148 raise schainpy.admin.SchainError("NEW FILE HAS A DIFFERENT SHAPE: ")
143 print(self.dataShape,newShape,"\n")
149 print(self.dataShape,newShape,"\n")
144 return 0
150 return 0
145 else:
151 else:
146 self.dataShape = fp.get('Raw11/Data/Samples/Data').shape[1:]
152 self.dataShape = fp.get('Raw11/Data/Samples/Data').shape[1:]
147
153
148
154
149 header = 'Raw11/Data/RadacHeader'
155 header = 'Raw11/Data/RadacHeader'
156 if self.nChannels == None:
157 expFile = fp['Setup/ExperimentFile'][()].decode()
158 linesExp = expFile.split("\n")
159 a = [line for line in linesExp if "nbeamcodes" in line]
160 self.nChannels = int(a[0][11:])
161
150 self.beamCodeByPulse = fp.get(header+'/BeamCode') # LIST OF BEAMS PER PROFILE, TO BE USED ON REARRANGE
162 self.beamCodeByPulse = fp.get(header+'/BeamCode') # LIST OF BEAMS PER PROFILE, TO BE USED ON REARRANGE
151 if (self.startDate> datetime.date(2021, 7, 15)) or self.flag_old_beams: #Se cambiΓ³ la forma de extracciΓ³n de Apuntes el 17 o forzar con flag de reorganizaciΓ³n
163 if (self.startDate > datetime.date(2021, 7, 15)) or self.flag_old_beams: #Se cambiΓ³ la forma de extracciΓ³n de Apuntes el 17 o forzar con flag de reorganizaciΓ³n
152 self.beamcodeFile = fp['Setup/Beamcodefile'][()].decode()
164 self.beamcodeFile = fp['Setup/Beamcodefile'][()].decode()
153 self.trueBeams = self.beamcodeFile.split("\n")
165 self.trueBeams = self.beamcodeFile.split("\n")
154 self.trueBeams.pop()#remove last
166 self.trueBeams.pop()#remove last
155 [self.realBeamCode.append(x) for x in self.trueBeams if x not in self.realBeamCode]
167 beams_idx = [k*self.nFFT for k in range(self.nChannels)]
156 self.beamCode = [int(x, 16) for x in self.realBeamCode]
168 beams = [self.trueBeams[b] for b in beams_idx]
169 self.beamCode = [int(x, 16) for x in beams]
170
157 else:
171 else:
158 _beamCode= fp.get('Raw11/Data/Beamcodes') #se usa la manera previa al cambio de apuntes
172 _beamCode= fp.get('Raw11/Data/Beamcodes') #se usa la manera previa al cambio de apuntes
159 self.beamCode = _beamCode[0,:]
173 self.beamCode = _beamCode[0,:]
160
174
161 if self.beamCodeMap == None:
175 if self.beamCodeMap == None:
162 self.beamCodeMap = fp['Setup/BeamcodeMap']
176 self.beamCodeMap = fp['Setup/BeamcodeMap']
163 for beam in self.beamCode:
177 for beam in self.beamCode:
164 beamAziElev = numpy.where(self.beamCodeMap[:,0]==beam)
178 beamAziElev = numpy.where(self.beamCodeMap[:,0]==beam)
165 beamAziElev = beamAziElev[0].squeeze()
179 beamAziElev = beamAziElev[0].squeeze()
166 self.azimuthList.append(self.beamCodeMap[beamAziElev,1])
180 self.azimuthList.append(self.beamCodeMap[beamAziElev,1])
167 self.elevationList.append(self.beamCodeMap[beamAziElev,2])
181 self.elevationList.append(self.beamCodeMap[beamAziElev,2])
168 #print("Beamssss: ",self.beamCodeMap[beamAziElev,1],self.beamCodeMap[beamAziElev,2])
182 #print("Beamssss: ",self.beamCodeMap[beamAziElev,1],self.beamCodeMap[beamAziElev,2])
169 #print(self.beamCode)
183 #print(self.beamCode)
170 #self.code = fp.get(header+'/Code') # NOT USE FOR THIS
184 #self.code = fp.get(header+'/Code') # NOT USE FOR THIS
171 self.frameCount = fp.get(header+'/FrameCount')# NOT USE FOR THIS
185 self.frameCount = fp.get(header+'/FrameCount')# NOT USE FOR THIS
172 self.modeGroup = fp.get(header+'/ModeGroup')# NOT USE FOR THIS
186 self.modeGroup = fp.get(header+'/ModeGroup')# NOT USE FOR THIS
173 self.nsamplesPulse = fp.get(header+'/NSamplesPulse')# TO GET NSA OR USING DATA FOR THAT
187 self.nsamplesPulse = fp.get(header+'/NSamplesPulse')# TO GET NSA OR USING DATA FOR THAT
174 self.pulseCount = fp.get(header+'/PulseCount')# NOT USE FOR THIS
188 self.pulseCount = fp.get(header+'/PulseCount')# NOT USE FOR THIS
175 self.radacTime = fp.get(header+'/RadacTime')# 1st TIME ON FILE ANDE CALCULATE THE REST WITH IPP*nindexprofile
189 self.radacTime = fp.get(header+'/RadacTime')# 1st TIME ON FILE ANDE CALCULATE THE REST WITH IPP*nindexprofile
176 self.timeCount = fp.get(header+'/TimeCount')# NOT USE FOR THIS
190 self.timeCount = fp.get(header+'/TimeCount')# NOT USE FOR THIS
177 self.timeStatus = fp.get(header+'/TimeStatus')# NOT USE FOR THIS
191 self.timeStatus = fp.get(header+'/TimeStatus')# NOT USE FOR THIS
178 self.rangeFromFile = fp.get('Raw11/Data/Samples/Range')
192 self.rangeFromFile = fp.get('Raw11/Data/Samples/Range')
179 self.frequency = fp.get('Rx/Frequency')
193 self.frequency = fp.get('Rx/Frequency')
180 txAus = fp.get('Raw11/Data/Pulsewidth')
194 txAus = fp.get('Raw11/Data/Pulsewidth')
181 self.baud = fp.get('Raw11/Data/TxBaud')
195 self.baud = fp.get('Raw11/Data/TxBaud')
182 sampleRate = fp.get('Rx/SampleRate')
196 sampleRate = fp.get('Rx/SampleRate')
183 self.__sampleRate = sampleRate[()]
197 self.__sampleRate = sampleRate[()]
184 self.nblocks = self.pulseCount.shape[0] #nblocks
198 self.nblocks = self.pulseCount.shape[0] #nblocks
185
199 self.profPerBlockRAW = self.pulseCount.shape[1] #profiles per block in raw data
186 self.nprofiles = self.pulseCount.shape[1] #nprofile
200 self.nprofiles = self.pulseCount.shape[1] #nprofile
187 self.nsa = self.nsamplesPulse[0,0] #ngates
201 self.nsa = self.nsamplesPulse[0,0] #ngates
188 self.nchannels = len(self.beamCode)
202 self.nchannels = len(self.beamCode)
189 self.ippSeconds = (self.radacTime[0][1] -self.radacTime[0][0]) #Ipp in seconds
203 self.ippSeconds = (self.radacTime[0][1] -self.radacTime[0][0]) #Ipp in seconds
190 #print("IPPS secs: ",self.ippSeconds)
204 #print("IPPS secs: ",self.ippSeconds)
191 #self.__waitForNewFile = self.nblocks # wait depending on the number of blocks since each block is 1 sec
205 #self.__waitForNewFile = self.nblocks # wait depending on the number of blocks since each block is 1 sec
192 self.__waitForNewFile = self.nblocks * self.nprofiles * self.ippSeconds # wait until new file is created
206 self.__waitForNewFile = self.nblocks * self.nprofiles * self.ippSeconds # wait until new file is created
193
207
194 #filling radar controller header parameters
208 #filling radar controller header parameters
195 self.__ippKm = self.ippSeconds *.15*1e6 # in km
209 self.__ippKm = self.ippSeconds *.15*1e6 # in km
196 self.__txA = (txAus[()])*.15 #(ipp[us]*.15km/1us) in km
210 self.__txA = (txAus[()])*.15 #(ipp[us]*.15km/1us) in km
197 self.__txB = 0
211 self.__txB = 0
198 nWindows=1
212 nWindows=1
199 self.__nSamples = self.nsa
213 self.__nSamples = self.nsa
200 self.__firstHeight = self.rangeFromFile[0][0]/1000 #in km
214 self.__firstHeight = self.rangeFromFile[0][0]/1000 #in km
201 self.__deltaHeight = (self.rangeFromFile[0][1] - self.rangeFromFile[0][0])/1000
215 self.__deltaHeight = (self.rangeFromFile[0][1] - self.rangeFromFile[0][0])/1000
202 #print("amisr-ipp:",self.ippSeconds, self.__ippKm)
216 #print("amisr-ipp:",self.ippSeconds, self.__ippKm)
203 #for now until understand why the code saved is different (code included even though code not in tuf file)
217 #for now until understand why the code saved is different (code included even though code not in tuf file)
204 #self.__codeType = 0
218 #self.__codeType = 0
205 # self.__nCode = None
219 # self.__nCode = None
206 # self.__nBaud = None
220 # self.__nBaud = None
207 self.__code = self.code
221 self.__code = self.code
208 self.__codeType = 0
222 self.__codeType = 0
209 if self.code != None:
223 if self.code != None:
210 self.__codeType = 1
224 self.__codeType = 1
211 self.__nCode = self.nCode
225 self.__nCode = self.nCode
212 self.__nBaud = self.nBaud
226 self.__nBaud = self.nBaud
213 #self.__code = 0
227 #self.__code = 0
214
228
215 #filling system header parameters
229 #filling system header parameters
216 self.__nSamples = self.nsa
230 self.__nSamples = self.nsa
217 self.newProfiles = self.nprofiles/self.nchannels
231 self.newProfiles = self.nprofiles/self.nchannels
218 self.__channelList = [n for n in range(self.nchannels)]
232 self.__channelList = [n for n in range(self.nchannels)]
219
233
220 self.__frequency = self.frequency[0][0]
234 self.__frequency = self.frequency[0][0]
221
235
222
236
223 return 1
237 return 1
224
238
225
239
226 def createBuffers(self):
240 def createBuffers(self):
227
241
228 pass
242 pass
229
243
230 def __setParameters(self,path='', startDate='',endDate='',startTime='', endTime='', walk=''):
244 def __setParameters(self,path='', startDate='',endDate='',startTime='', endTime='', walk=''):
231 self.path = path
245 self.path = path
232 self.startDate = startDate
246 self.startDate = startDate
233 self.endDate = endDate
247 self.endDate = endDate
234 self.startTime = startTime
248 self.startTime = startTime
235 self.endTime = endTime
249 self.endTime = endTime
236 self.walk = walk
250 self.walk = walk
237
251
238 def __checkPath(self):
252 def __checkPath(self):
239 if os.path.exists(self.path):
253 if os.path.exists(self.path):
240 self.status = 1
254 self.status = 1
241 else:
255 else:
242 self.status = 0
256 self.status = 0
243 print('Path:%s does not exists'%self.path)
257 print('Path:%s does not exists'%self.path)
244
258
245 return
259 return
246
260
247
261
248 def __selDates(self, amisr_dirname_format):
262 def __selDates(self, amisr_dirname_format):
249 try:
263 try:
250 year = int(amisr_dirname_format[0:4])
264 year = int(amisr_dirname_format[0:4])
251 month = int(amisr_dirname_format[4:6])
265 month = int(amisr_dirname_format[4:6])
252 dom = int(amisr_dirname_format[6:8])
266 dom = int(amisr_dirname_format[6:8])
253 thisDate = datetime.date(year,month,dom)
267 thisDate = datetime.date(year,month,dom)
254 #margen de un dΓ­a extra, igual luego se filtra for fecha y hora
268 #margen de un dΓ­a extra, igual luego se filtra for fecha y hora
255 if (thisDate>=(self.startDate - datetime.timedelta(days=self.margin_days)) and thisDate <= (self.endDate)+ datetime.timedelta(days=1)):
269 if (thisDate>=(self.startDate - datetime.timedelta(days=self.margin_days)) and thisDate <= (self.endDate)+ datetime.timedelta(days=1)):
256 return amisr_dirname_format
270 return amisr_dirname_format
257 except:
271 except:
258 return None
272 return None
259
273
260
274
261 def __findDataForDates(self,online=False):
275 def __findDataForDates(self,online=False):
262
276
263 if not(self.status):
277 if not(self.status):
264 return None
278 return None
265
279
266 pat = '\d+.\d+'
280 pat = '\d+.\d+'
267 dirnameList = [re.search(pat,x) for x in os.listdir(self.path)]
281 dirnameList = [re.search(pat,x) for x in os.listdir(self.path)]
268 dirnameList = [x for x in dirnameList if x!=None]
282 dirnameList = [x for x in dirnameList if x!=None]
269 dirnameList = [x.string for x in dirnameList]
283 dirnameList = [x.string for x in dirnameList]
270 if not(online):
284 if not(online):
271 dirnameList = [self.__selDates(x) for x in dirnameList]
285 dirnameList = [self.__selDates(x) for x in dirnameList]
272 dirnameList = [x for x in dirnameList if x!=None]
286 dirnameList = [x for x in dirnameList if x!=None]
273 if len(dirnameList)>0:
287 if len(dirnameList)>0:
274 self.status = 1
288 self.status = 1
275 self.dirnameList = dirnameList
289 self.dirnameList = dirnameList
276 self.dirnameList.sort()
290 self.dirnameList.sort()
277 else:
291 else:
278 self.status = 0
292 self.status = 0
279 return None
293 return None
280
294
281 def __getTimeFromData(self):
295 def __getTimeFromData(self):
282 startDateTime_Reader = datetime.datetime.combine(self.startDate,self.startTime)
296 startDateTime_Reader = datetime.datetime.combine(self.startDate,self.startTime)
283 endDateTime_Reader = datetime.datetime.combine(self.endDate,self.endTime)
297 endDateTime_Reader = datetime.datetime.combine(self.endDate,self.endTime)
284
298
285 print('Filtering Files from %s to %s'%(startDateTime_Reader, endDateTime_Reader))
299 print('Filtering Files from %s to %s'%(startDateTime_Reader, endDateTime_Reader))
286 print('........................................')
300 print('........................................')
287 filter_filenameList = []
301 filter_filenameList = []
288 self.filenameList.sort()
302 self.filenameList.sort()
289 total_files = len(self.filenameList)
303 total_files = len(self.filenameList)
290 #for i in range(len(self.filenameList)-1):
304 #for i in range(len(self.filenameList)-1):
291 for i in range(total_files):
305 for i in range(total_files):
292 filename = self.filenameList[i]
306 filename = self.filenameList[i]
293 #print("file-> ",filename)
307 #print("file-> ",filename)
294 try:
308 try:
295 fp = h5py.File(filename,'r')
309 fp = h5py.File(filename,'r')
296 time_str = fp.get('Time/RadacTimeString')
310 time_str = fp.get('Time/RadacTimeString')
297
311
298 startDateTimeStr_File = time_str[0][0].decode('UTF-8').split('.')[0]
312 startDateTimeStr_File = time_str[0][0].decode('UTF-8').split('.')[0]
299 #startDateTimeStr_File = "2019-12-16 09:21:11"
313 #startDateTimeStr_File = "2019-12-16 09:21:11"
300 junk = time.strptime(startDateTimeStr_File, '%Y-%m-%d %H:%M:%S')
314 junk = time.strptime(startDateTimeStr_File, '%Y-%m-%d %H:%M:%S')
301 startDateTime_File = datetime.datetime(junk.tm_year,junk.tm_mon,junk.tm_mday,junk.tm_hour, junk.tm_min, junk.tm_sec)
315 startDateTime_File = datetime.datetime(junk.tm_year,junk.tm_mon,junk.tm_mday,junk.tm_hour, junk.tm_min, junk.tm_sec)
302
316
303 #endDateTimeStr_File = "2019-12-16 11:10:11"
317 #endDateTimeStr_File = "2019-12-16 11:10:11"
304 endDateTimeStr_File = time_str[-1][-1].decode('UTF-8').split('.')[0]
318 endDateTimeStr_File = time_str[-1][-1].decode('UTF-8').split('.')[0]
305 junk = time.strptime(endDateTimeStr_File, '%Y-%m-%d %H:%M:%S')
319 junk = time.strptime(endDateTimeStr_File, '%Y-%m-%d %H:%M:%S')
306 endDateTime_File = datetime.datetime(junk.tm_year,junk.tm_mon,junk.tm_mday,junk.tm_hour, junk.tm_min, junk.tm_sec)
320 endDateTime_File = datetime.datetime(junk.tm_year,junk.tm_mon,junk.tm_mday,junk.tm_hour, junk.tm_min, junk.tm_sec)
307
321
308 fp.close()
322 fp.close()
309
323
310 #print("check time", startDateTime_File)
324 #print("check time", startDateTime_File)
311 if self.timezone == 'lt':
325 if self.timezone == 'lt':
312 startDateTime_File = startDateTime_File - datetime.timedelta(minutes = 300)
326 startDateTime_File = startDateTime_File - datetime.timedelta(minutes = 300)
313 endDateTime_File = endDateTime_File - datetime.timedelta(minutes = 300)
327 endDateTime_File = endDateTime_File - datetime.timedelta(minutes = 300)
314 if (startDateTime_File >=startDateTime_Reader and endDateTime_File<=endDateTime_Reader):
328 if (startDateTime_File >=startDateTime_Reader and endDateTime_File<=endDateTime_Reader):
315 filter_filenameList.append(filename)
329 filter_filenameList.append(filename)
316
330
317 if (startDateTime_File>endDateTime_Reader):
331 if (startDateTime_File>endDateTime_Reader):
318 break
332 break
319 except Exception as e:
333 except Exception as e:
320 log.warning("Error opening file {} -> {}".format(os.path.split(filename)[1],e))
334 log.warning("Error opening file {} -> {}".format(os.path.split(filename)[1],e))
321
335
322 filter_filenameList.sort()
336 filter_filenameList.sort()
323 self.filenameList = filter_filenameList
337 self.filenameList = filter_filenameList
324
338
325 return 1
339 return 1
326
340
327 def __filterByGlob1(self, dirName):
341 def __filterByGlob1(self, dirName):
328 filter_files = glob.glob1(dirName, '*.*%s'%self.extension_file)
342 filter_files = glob.glob1(dirName, '*.*%s'%self.extension_file)
329 filter_files.sort()
343 filter_files.sort()
330 filterDict = {}
344 filterDict = {}
331 filterDict.setdefault(dirName)
345 filterDict.setdefault(dirName)
332 filterDict[dirName] = filter_files
346 filterDict[dirName] = filter_files
333 return filterDict
347 return filterDict
334
348
335 def __getFilenameList(self, fileListInKeys, dirList):
349 def __getFilenameList(self, fileListInKeys, dirList):
336 for value in fileListInKeys:
350 for value in fileListInKeys:
337 dirName = list(value.keys())[0]
351 dirName = list(value.keys())[0]
338 for file in value[dirName]:
352 for file in value[dirName]:
339 filename = os.path.join(dirName, file)
353 filename = os.path.join(dirName, file)
340 self.filenameList.append(filename)
354 self.filenameList.append(filename)
341
355
342
356
343 def __selectDataForTimes(self, online=False):
357 def __selectDataForTimes(self, online=False):
344 #aun no esta implementado el filtro for tiempo
358 #aun no esta implementado el filtro for tiempo
345 if not(self.status):
359 if not(self.status):
346 return None
360 return None
347
361
348 dirList = [os.path.join(self.path,x) for x in self.dirnameList]
362 dirList = [os.path.join(self.path,x) for x in self.dirnameList]
349 fileListInKeys = [self.__filterByGlob1(x) for x in dirList]
363 fileListInKeys = [self.__filterByGlob1(x) for x in dirList]
350 self.__getFilenameList(fileListInKeys, dirList)
364 self.__getFilenameList(fileListInKeys, dirList)
351 if not(online):
365 if not(online):
352 #filtro por tiempo
366 #filtro por tiempo
353 if not(self.all):
367 if not(self.all):
354 self.__getTimeFromData()
368 self.__getTimeFromData()
355
369
356 if len(self.filenameList)>0:
370 if len(self.filenameList)>0:
357 self.status = 1
371 self.status = 1
358 self.filenameList.sort()
372 self.filenameList.sort()
359 else:
373 else:
360 self.status = 0
374 self.status = 0
361 return None
375 return None
362
376
363 else:
377 else:
364 #get the last file - 1
378 #get the last file - 1
365 self.filenameList = [self.filenameList[-2]]
379 self.filenameList = [self.filenameList[-2]]
366 new_dirnameList = []
380 new_dirnameList = []
367 for dirname in self.dirnameList:
381 for dirname in self.dirnameList:
368 junk = numpy.array([dirname in x for x in self.filenameList])
382 junk = numpy.array([dirname in x for x in self.filenameList])
369 junk_sum = junk.sum()
383 junk_sum = junk.sum()
370 if junk_sum > 0:
384 if junk_sum > 0:
371 new_dirnameList.append(dirname)
385 new_dirnameList.append(dirname)
372 self.dirnameList = new_dirnameList
386 self.dirnameList = new_dirnameList
373 return 1
387 return 1
374
388
375 def searchFilesOnLine(self, path, startDate, endDate, startTime=datetime.time(0,0,0),
389 def searchFilesOnLine(self, path, startDate, endDate, startTime=datetime.time(0,0,0),
376 endTime=datetime.time(23,59,59),walk=True):
390 endTime=datetime.time(23,59,59),walk=True):
377
391
378 if endDate ==None:
392 if endDate ==None:
379 startDate = datetime.datetime.utcnow().date()
393 startDate = datetime.datetime.utcnow().date()
380 endDate = datetime.datetime.utcnow().date()
394 endDate = datetime.datetime.utcnow().date()
381
395
382 self.__setParameters(path=path, startDate=startDate, endDate=endDate,startTime = startTime,endTime=endTime, walk=walk)
396 self.__setParameters(path=path, startDate=startDate, endDate=endDate,startTime = startTime,endTime=endTime, walk=walk)
383
397
384 self.__checkPath()
398 self.__checkPath()
385
399
386 self.__findDataForDates(online=True)
400 self.__findDataForDates(online=True)
387
401
388 self.dirnameList = [self.dirnameList[-1]]
402 self.dirnameList = [self.dirnameList[-1]]
389
403
390 self.__selectDataForTimes(online=True)
404 self.__selectDataForTimes(online=True)
391
405
392 return
406 return
393
407
394
408
395 def searchFilesOffLine(self,
409 def searchFilesOffLine(self,
396 path,
410 path,
397 startDate,
411 startDate,
398 endDate,
412 endDate,
399 startTime=datetime.time(0,0,0),
413 startTime=datetime.time(0,0,0),
400 endTime=datetime.time(23,59,59),
414 endTime=datetime.time(23,59,59),
401 walk=True):
415 walk=True):
402
416
403 self.__setParameters(path, startDate, endDate, startTime, endTime, walk)
417 self.__setParameters(path, startDate, endDate, startTime, endTime, walk)
404
418
405 self.__checkPath()
419 self.__checkPath()
406
420
407 self.__findDataForDates()
421 self.__findDataForDates()
408
422
409 self.__selectDataForTimes()
423 self.__selectDataForTimes()
410
424
411 for i in range(len(self.filenameList)):
425 for i in range(len(self.filenameList)):
412 print("%s" %(self.filenameList[i]))
426 print("%s" %(self.filenameList[i]))
413
427
414 return
428 return
415
429
416 def __setNextFileOffline(self):
430 def __setNextFileOffline(self):
417
431
418 try:
432 try:
419 self.filename = self.filenameList[self.fileIndex]
433 self.filename = self.filenameList[self.fileIndex]
420 self.amisrFilePointer = h5py.File(self.filename,'r')
434 self.amisrFilePointer = h5py.File(self.filename,'r')
421 self.fileIndex += 1
435 self.fileIndex += 1
422 except:
436 except:
423 self.flagNoMoreFiles = 1
437 self.flagNoMoreFiles = 1
424 raise schainpy.admin.SchainError('No more files to read')
438 raise schainpy.admin.SchainError('No more files to read')
425 return 0
439 return 0
426
440
427 self.flagIsNewFile = 1
441 self.flagIsNewFile = 1
428 print("Setting the file: %s"%self.filename)
442 print("Setting the file: %s"%self.filename)
429
443
430 return 1
444 return 1
431
445
432
446
433 def __setNextFileOnline(self):
447 def __setNextFileOnline(self):
434 filename = self.filenameList[0]
448 filename = self.filenameList[0]
435 if self.__filename_online != None:
449 if self.__filename_online != None:
436 self.__selectDataForTimes(online=True)
450 self.__selectDataForTimes(online=True)
437 filename = self.filenameList[0]
451 filename = self.filenameList[0]
438 wait = 0
452 wait = 0
439 self.__waitForNewFile=300 ## DEBUG:
453 self.__waitForNewFile=300 ## DEBUG:
440 while self.__filename_online == filename:
454 while self.__filename_online == filename:
441 print('waiting %d seconds to get a new file...'%(self.__waitForNewFile))
455 print('waiting %d seconds to get a new file...'%(self.__waitForNewFile))
442 if wait == 5:
456 if wait == 5:
443 self.flagNoMoreFiles = 1
457 self.flagNoMoreFiles = 1
444 return 0
458 return 0
445 sleep(self.__waitForNewFile)
459 sleep(self.__waitForNewFile)
446 self.__selectDataForTimes(online=True)
460 self.__selectDataForTimes(online=True)
447 filename = self.filenameList[0]
461 filename = self.filenameList[0]
448 wait += 1
462 wait += 1
449
463
450 self.__filename_online = filename
464 self.__filename_online = filename
451
465
452 self.amisrFilePointer = h5py.File(filename,'r')
466 self.amisrFilePointer = h5py.File(filename,'r')
453 self.flagIsNewFile = 1
467 self.flagIsNewFile = 1
454 self.filename = filename
468 self.filename = filename
455 print("Setting the file: %s"%self.filename)
469 print("Setting the file: %s"%self.filename)
456 return 1
470 return 1
457
471
458
472
459 def readData(self):
473 def readData(self):
460 buffer = self.amisrFilePointer.get('Raw11/Data/Samples/Data')
474 buffer = self.amisrFilePointer.get('Raw11/Data/Samples/Data')
461 re = buffer[:,:,:,0]
475 re = buffer[:,:,:,0]
462 im = buffer[:,:,:,1]
476 im = buffer[:,:,:,1]
463 dataset = re + im*1j
477 dataset = re + im*1j
464
478
465 self.radacTime = self.amisrFilePointer.get('Raw11/Data/RadacHeader/RadacTime')
479 self.radacTime = self.amisrFilePointer.get('Raw11/Data/RadacHeader/RadacTime')
466 timeset = self.radacTime[:,0]
480 timeset = self.radacTime[:,0]
467
481
468 return dataset,timeset
482 return dataset,timeset
469
483
470 def reshapeData(self):
484 def reshapeData(self):
471 #self.beamCodeByPulse, self.beamCode, self.nblocks, self.nprofiles, self.nsa,
485 #print(self.beamCodeByPulse, self.beamCode, self.nblocks, self.nprofiles, self.nsa)
472 channels = self.beamCodeByPulse[0,:]
486 channels = self.beamCodeByPulse[0,:]
473 nchan = self.nchannels
487 nchan = self.nchannels
474 #self.newProfiles = self.nprofiles/nchan #must be defined on filljroheader
488 #self.newProfiles = self.nprofiles/nchan #must be defined on filljroheader
475 nblocks = self.nblocks
489 nblocks = self.nblocks
476 nsamples = self.nsa
490 nsamples = self.nsa
477
491 #print("Channels: ",self.nChannels)
478 #Dimensions : nChannels, nProfiles, nSamples
492 #Dimensions : nChannels, nProfiles, nSamples
479 new_block = numpy.empty((nblocks, nchan, numpy.int_(self.newProfiles), nsamples), dtype="complex64")
493 new_block = numpy.empty((nblocks, nchan, numpy.int_(self.newProfiles), nsamples), dtype="complex64")
480 ############################################
494 ############################################
495 profPerCH = int(self.profPerBlockRAW / (self.nFFT * self.nChannels))
481
496
482 for thisChannel in range(nchan):
497 for thisChannel in range(nchan):
483 new_block[:,thisChannel,:,:] = self.dataset[:,numpy.where(channels==self.beamCode[thisChannel])[0],:]
484
498
499 idx_ch = [thisChannel+k*nchan for k in range(profPerCH)]
500 idx_ch = numpy.array(idx_ch, dtype=int)
501 #print(thisChannel,idx_ch)
502 #print(numpy.where(channels==self.beamCode[thisChannel])[0])
503 #new_block[:,thisChannel,:,:] = self.dataset[:,numpy.where(channels==self.beamCode[thisChannel])[0],:]
504 new_block[:,thisChannel,:,:] = self.dataset[:,idx_ch,:]
485
505
486 new_block = numpy.transpose(new_block, (1,0,2,3))
506 new_block = numpy.transpose(new_block, (1,0,2,3))
487 new_block = numpy.reshape(new_block, (nchan,-1, nsamples))
507 new_block = numpy.reshape(new_block, (nchan,-1, nsamples))
488
508
489 return new_block
509 return new_block
490
510
491 def updateIndexes(self):
511 def updateIndexes(self):
492
512
493 pass
513 pass
494
514
495 def fillJROHeader(self):
515 def fillJROHeader(self):
496
516
497 #fill radar controller header
517 #fill radar controller header
498 self.dataOut.radarControllerHeaderObj = RadarControllerHeader(ipp=self.__ippKm,
518 self.dataOut.radarControllerHeaderObj = RadarControllerHeader(ipp=self.__ippKm,
499 txA=self.__txA,
519 txA=self.__txA,
500 txB=0,
520 txB=0,
501 nWindows=1,
521 nWindows=1,
502 nHeights=self.__nSamples,
522 nHeights=self.__nSamples,
503 firstHeight=self.__firstHeight,
523 firstHeight=self.__firstHeight,
504 deltaHeight=self.__deltaHeight,
524 deltaHeight=self.__deltaHeight,
505 codeType=self.__codeType,
525 codeType=self.__codeType,
506 nCode=self.__nCode, nBaud=self.__nBaud,
526 nCode=self.__nCode, nBaud=self.__nBaud,
507 code = self.__code,
527 code = self.__code,
508 fClock=self.__sampleRate)
528 fClock=self.__sampleRate)
509 #fill system header
529 #fill system header
510 self.dataOut.systemHeaderObj = SystemHeader(nSamples=self.__nSamples,
530 self.dataOut.systemHeaderObj = SystemHeader(nSamples=self.__nSamples,
511 nProfiles=self.newProfiles,
531 nProfiles=self.newProfiles,
512 nChannels=len(self.__channelList),
532 nChannels=len(self.__channelList),
513 adcResolution=14,
533 adcResolution=14,
514 pciDioBusWidth=32)
534 pciDioBusWidth=32)
515
535
516 self.dataOut.type = "Voltage"
536 self.dataOut.type = "Voltage"
517 self.dataOut.data = None
537 self.dataOut.data = None
518 self.dataOut.dtype = numpy.dtype([('real','<i8'),('imag','<i8')])
538 self.dataOut.dtype = numpy.dtype([('real','<i8'),('imag','<i8')])
519 # self.dataOut.nChannels = 0
539 # self.dataOut.nChannels = 0
520
540
521 # self.dataOut.nHeights = 0
541 # self.dataOut.nHeights = 0
522
542
523 self.dataOut.nProfiles = self.newProfiles*self.nblocks
543 self.dataOut.nProfiles = self.newProfiles*self.nblocks
524 #self.dataOut.heightList = self.__firstHeigth + numpy.arange(self.__nSamples, dtype = numpy.float)*self.__deltaHeigth
544 #self.dataOut.heightList = self.__firstHeigth + numpy.arange(self.__nSamples, dtype = numpy.float)*self.__deltaHeigth
525 ranges = numpy.reshape(self.rangeFromFile[()],(-1))
545 ranges = numpy.reshape(self.rangeFromFile[()],(-1))
526 self.dataOut.heightList = ranges/1000.0 #km
546 self.dataOut.heightList = ranges/1000.0 #km
527 self.dataOut.channelList = self.__channelList
547 self.dataOut.channelList = self.__channelList
528 self.dataOut.blocksize = self.dataOut.nChannels * self.dataOut.nHeights
548 self.dataOut.blocksize = self.dataOut.nChannels * self.dataOut.nHeights
529
549
530 # self.dataOut.channelIndexList = None
550 # self.dataOut.channelIndexList = None
531
551
532
552
533 self.dataOut.azimuthList = numpy.array(self.azimuthList)
553 self.dataOut.azimuthList = numpy.array(self.azimuthList)
534 self.dataOut.elevationList = numpy.array(self.elevationList)
554 self.dataOut.elevationList = numpy.array(self.elevationList)
535 self.dataOut.codeList = numpy.array(self.beamCode)
555 self.dataOut.codeList = numpy.array(self.beamCode)
536 #print(self.dataOut.elevationList)
556 #print(self.dataOut.elevationList)
537 self.dataOut.flagNoData = True
557 self.dataOut.flagNoData = True
538
558
539 #Set to TRUE if the data is discontinuous
559 #Set to TRUE if the data is discontinuous
540 self.dataOut.flagDiscontinuousBlock = False
560 self.dataOut.flagDiscontinuousBlock = False
541
561
542 self.dataOut.utctime = None
562 self.dataOut.utctime = None
543
563
544 #self.dataOut.timeZone = -5 #self.__timezone/60 #timezone like jroheader, difference in minutes between UTC and localtime
564 #self.dataOut.timeZone = -5 #self.__timezone/60 #timezone like jroheader, difference in minutes between UTC and localtime
545 if self.timezone == 'lt':
565 if self.timezone == 'lt':
546 self.dataOut.timeZone = time.timezone / 60. #get the timezone in minutes
566 self.dataOut.timeZone = time.timezone / 60. #get the timezone in minutes
547 else:
567 else:
548 self.dataOut.timeZone = 0 #by default time is UTC
568 self.dataOut.timeZone = 0 #by default time is UTC
549
569
550 self.dataOut.dstFlag = 0
570 self.dataOut.dstFlag = 0
551 self.dataOut.errorCount = 0
571 self.dataOut.errorCount = 0
552 self.dataOut.nCohInt = 1
572 self.dataOut.nCohInt = 1
553 self.dataOut.flagDecodeData = False #asumo que la data esta decodificada
573 self.dataOut.flagDecodeData = False #asumo que la data esta decodificada
554 self.dataOut.flagDeflipData = False #asumo que la data esta sin flip
574 self.dataOut.flagDeflipData = False #asumo que la data esta sin flip
555 self.dataOut.flagShiftFFT = False
575 self.dataOut.flagShiftFFT = False
556 self.dataOut.ippSeconds = self.ippSeconds
576 self.dataOut.ippSeconds = self.ippSeconds
557 self.dataOut.radar_ipp = self.ippSeconds
577 self.dataOut.radar_ipp = self.ippSeconds
558 self.dataOut.pulseLength_TxA = self.__txA/0.15
578 self.dataOut.pulseLength_TxA = self.__txA/0.15
559 self.dataOut.deltaHeight = self.__deltaHeight
579 self.dataOut.deltaHeight = self.__deltaHeight
560 #Time interval between profiles
580 #Time interval between profiles
561 #self.dataOut.timeInterval = self.dataOut.ippSeconds * self.dataOut.nCohInt
581 #self.dataOut.timeInterval = self.dataOut.ippSeconds * self.dataOut.nCohInt
562
582
563 self.dataOut.frequency = self.__frequency
583 self.dataOut.frequency = self.__frequency
564 self.dataOut.realtime = self.online
584 self.dataOut.realtime = self.online
565 pass
585 pass
566
586
567 def readNextFile(self,online=False):
587 def readNextFile(self,online=False):
568
588
569 if not(online):
589 if not(online):
570 newFile = self.__setNextFileOffline()
590 newFile = self.__setNextFileOffline()
571 else:
591 else:
572 newFile = self.__setNextFileOnline()
592 newFile = self.__setNextFileOnline()
573
593
574 if not(newFile):
594 if not(newFile):
575 self.dataOut.error = True
595 self.dataOut.error = True
576 return 0
596 return 0
577
597
578 if not self.readAMISRHeader(self.amisrFilePointer):
598 if not self.readAMISRHeader(self.amisrFilePointer):
579 self.dataOut.error = True
599 self.dataOut.error = True
580 return 0
600 return 0
581
601
582 self.createBuffers()
602 self.createBuffers()
583 self.fillJROHeader()
603 self.fillJROHeader()
584
604
585 #self.__firstFile = False
605 #self.__firstFile = False
586
606
587
607
588
608
589 self.dataset,self.timeset = self.readData()
609 self.dataset,self.timeset = self.readData()
590
610
591 if self.endDate!=None:
611 if self.endDate!=None:
592 endDateTime_Reader = datetime.datetime.combine(self.endDate,self.endTime)
612 endDateTime_Reader = datetime.datetime.combine(self.endDate,self.endTime)
593 time_str = self.amisrFilePointer.get('Time/RadacTimeString')
613 time_str = self.amisrFilePointer.get('Time/RadacTimeString')
594 startDateTimeStr_File = time_str[0][0].decode('UTF-8').split('.')[0]
614 startDateTimeStr_File = time_str[0][0].decode('UTF-8').split('.')[0]
595 junk = time.strptime(startDateTimeStr_File, '%Y-%m-%d %H:%M:%S')
615 junk = time.strptime(startDateTimeStr_File, '%Y-%m-%d %H:%M:%S')
596 startDateTime_File = datetime.datetime(junk.tm_year,junk.tm_mon,junk.tm_mday,junk.tm_hour, junk.tm_min, junk.tm_sec)
616 startDateTime_File = datetime.datetime(junk.tm_year,junk.tm_mon,junk.tm_mday,junk.tm_hour, junk.tm_min, junk.tm_sec)
597 if self.timezone == 'lt':
617 if self.timezone == 'lt':
598 startDateTime_File = startDateTime_File - datetime.timedelta(minutes = 300)
618 startDateTime_File = startDateTime_File - datetime.timedelta(minutes = 300)
599 if (startDateTime_File>endDateTime_Reader):
619 if (startDateTime_File>endDateTime_Reader):
600 return 0
620 return 0
601
621
602 self.jrodataset = self.reshapeData()
622 self.jrodataset = self.reshapeData()
603 #----self.updateIndexes()
623 #----self.updateIndexes()
604 self.profileIndex = 0
624 self.profileIndex = 0
605
625
606 return 1
626 return 1
607
627
608
628
609 def __hasNotDataInBuffer(self):
629 def __hasNotDataInBuffer(self):
610 if self.profileIndex >= (self.newProfiles*self.nblocks):
630 if self.profileIndex >= (self.newProfiles*self.nblocks):
611 return 1
631 return 1
612 return 0
632 return 0
613
633
614
634
615 def getData(self):
635 def getData(self):
616
636
617 if self.flagNoMoreFiles:
637 if self.flagNoMoreFiles:
618 self.dataOut.flagNoData = True
638 self.dataOut.flagNoData = True
619 return 0
639 return 0
620
640
621 if self.profileIndex >= (self.newProfiles*self.nblocks): #
641 if self.profileIndex >= (self.newProfiles*self.nblocks): #
622 #if self.__hasNotDataInBuffer():
642 #if self.__hasNotDataInBuffer():
623 if not (self.readNextFile(self.online)):
643 if not (self.readNextFile(self.online)):
624 return 0
644 return 0
625
645
626
646
627 if self.dataset is None: # setear esta condicion cuando no hayan datos por leer
647 if self.dataset is None: # setear esta condicion cuando no hayan datos por leer
628 self.dataOut.flagNoData = True
648 self.dataOut.flagNoData = True
629 return 0
649 return 0
630
650
631 #self.dataOut.data = numpy.reshape(self.jrodataset[self.profileIndex,:],(1,-1))
651 #self.dataOut.data = numpy.reshape(self.jrodataset[self.profileIndex,:],(1,-1))
632
652
633 self.dataOut.data = self.jrodataset[:,self.profileIndex,:]
653 self.dataOut.data = self.jrodataset[:,self.profileIndex,:]
634
654
635 #print("R_t",self.timeset)
655 #print("R_t",self.timeset)
636
656
637 #self.dataOut.utctime = self.jrotimeset[self.profileIndex]
657 #self.dataOut.utctime = self.jrotimeset[self.profileIndex]
638 #verificar basic header de jro data y ver si es compatible con este valor
658 #verificar basic header de jro data y ver si es compatible con este valor
639 #self.dataOut.utctime = self.timeset + (self.profileIndex * self.ippSeconds * self.nchannels)
659 #self.dataOut.utctime = self.timeset + (self.profileIndex * self.ippSeconds * self.nchannels)
640 indexprof = numpy.mod(self.profileIndex, self.newProfiles)
660 indexprof = numpy.mod(self.profileIndex, self.newProfiles)
641 indexblock = self.profileIndex/self.newProfiles
661 indexblock = self.profileIndex/self.newProfiles
642 #print (indexblock, indexprof)
662 #print (indexblock, indexprof)
643 diffUTC = 0
663 diffUTC = 0
644 t_comp = (indexprof * self.ippSeconds * self.nchannels) + diffUTC #
664 t_comp = (indexprof * self.ippSeconds * self.nchannels) + diffUTC #
645
665
646 #print("utc :",indexblock," __ ",t_comp)
666 #print("utc :",indexblock," __ ",t_comp)
647 #print(numpy.shape(self.timeset))
667 #print(numpy.shape(self.timeset))
648 self.dataOut.utctime = self.timeset[numpy.int_(indexblock)] + t_comp
668 self.dataOut.utctime = self.timeset[numpy.int_(indexblock)] + t_comp
649 #self.dataOut.utctime = self.timeset[self.profileIndex] + t_comp
669 #self.dataOut.utctime = self.timeset[self.profileIndex] + t_comp
650
670
651 self.dataOut.profileIndex = self.profileIndex
671 self.dataOut.profileIndex = self.profileIndex
652 #print("N profile:",self.profileIndex,self.newProfiles,self.nblocks,self.dataOut.utctime)
672 #print("N profile:",self.profileIndex,self.newProfiles,self.nblocks,self.dataOut.utctime)
653 self.dataOut.flagNoData = False
673 self.dataOut.flagNoData = False
654 # if indexprof == 0:
674 # if indexprof == 0:
655 # print("kamisr: ",self.dataOut.utctime)
675 # print("kamisr: ",self.dataOut.utctime)
656
676
657 self.profileIndex += 1
677 self.profileIndex += 1
658
678
659 return self.dataOut.data #retorno necesario??
679 return self.dataOut.data #retorno necesario??
660
680
661
681
662 def run(self, **kwargs):
682 def run(self, **kwargs):
663 '''
683 '''
664 This method will be called many times so here you should put all your code
684 This method will be called many times so here you should put all your code
665 '''
685 '''
666 #print("running kamisr")
686 #print("running kamisr")
667 if not self.isConfig:
687 if not self.isConfig:
668 self.setup(**kwargs)
688 self.setup(**kwargs)
669 self.isConfig = True
689 self.isConfig = True
670
690
671 self.getData()
691 self.getData()
General Comments 0
You need to be logged in to leave comments. Login now