##// END OF EJS Templates
-Actualización del modulo SpectraProcessor, SchainPlot y schainPlotLib...
Miguel Valdez -
r153:e56dcbb72a43
parent child
Show More
@@ -40,11 +40,13 class JROData:
40 40
41 41 channelIndexList = None
42 42
43 flagNoData = False
43 flagNoData = True
44 44
45 45 flagTimeBlock = False
46 46
47 dataUtcTime = None
47 utctime = None
48
49 blocksize = None
48 50
49 51 nCode = None
50 52
@@ -115,9 +117,11 class Voltage(JROData):
115 117
116 118 self.flagTimeBlock = False
117 119
118 self.dataUtcTime = None
120 self.utctime = None
119 121
120 122 self.nCohInt = None
123
124 self.blocksize = None
121 125
122 126 class Spectra(JROData):
123 127
@@ -169,10 +173,12 class Spectra(JROData):
169 173
170 174 self.flagTimeBlock = False
171 175
172 self.dataUtcTime = None
176 self.utctime = None
173 177
174 178 self.nIncohInt = None
175 179
180 self.blocksize = None
181
176 182
177 183 class SpectraHeis(JROData):
178 184
@@ -217,3 +223,7 class SpectraHeis(JROData):
217 223 self.flagTimeBlock = False
218 224
219 225 self.nPairs = 0
226
227 self.utctime = None
228
229 self.blocksize = None
@@ -5,57 +5,80 import os
5 5 from schainPlotLib import Driver
6 6
7 7 class Figure:
8
8 9 __isDriverOpen = False
9 10 __isFigureOpen = False
10 11 __isConfig = False
12
11 13 drvObj = None
14 driver = None
12 15 idfigure = None
13 16 nframes = None
14 17 wintitle = None
15 18 colormap = None
16 driver = None
17 19 overplot = None
20 colorbar = None
21
22 frameObjList = []
23
24 xw = None
25 yw = None
26
18 27 xmin = None
19 28 xmax = None
20 29 ymin = None
21 30 ymax = None
31
22 32 minvalue = None
23 33 maxvalue = None
24 34 deltax = None
25 35 deltay = None
26 frameObjList = []
36
37
27 38 figuretitle = ""
28 39 xrangestep = None
29 40
30 41 def __init__(self,idfigure, nframes, wintitle, xw=600, yw=800, overplot=0, driver='plplot', colormap=None, colorbar= True, *args):
31 42
43 self.__isDriverOpen = False
44 self.__isFigureOpen = False
45 self.__isConfig = False
46
32 47 self.drvObj = Driver(driver, idfigure, xw, yw, wintitle, overplot, colormap, colorbar)
33 48 self.driver = driver
34 49 self.idfigure = idfigure
35 self.xw = xw
36 self.yw = yw
37 50 self.nframes = nframes
38 51 self.wintitle = wintitle
39 52 self.colormap = colormap
40 53 self.overplot = overplot
41 54 self.colorbar = colorbar
55
56 self.xw = xw
57 self.yw = yw
58
59 self.frameObjList = []
60
42 61 # self.showGraph1 = args[0]
43 62 # self.showGraph2 = args[1]
44 63
45 64 self.drvObj.driver.setFigure()
46 65 self.drvObj.driver.setColormap(colormap)
47
48
49 66
50 67 def __openDriver(self):
68
51 69 self.drvObj.driver.openDriver()
52 70
53 71 def __initFigure(self):
72
54 73 nrows, ncolumns = self.getSubplots()
55 74 self.drvObj.driver.openFigure()
56 75 self.drvObj.driver.setFigTitle(self.figuretitle)
57 76 self.drvObj.driver.setSubPlots(nrows, ncolumns)
58 77
78 def selectFigure(self):
79
80 self.drvObj.driver.selectFigure()
81
59 82 def __isOutOfXRange(self,x):
60 83 try:
61 84 if ((x>=self.xmin) and (x<self.xmax)):
@@ -76,6 +99,7 class Figure:
76 99 raise ValueError, "No implemented"
77 100
78 101 def save(self,filename):
102
79 103 self.drvObj.driver.save(filename)
80 104
81 105 def plot1DArray(self, data1D, x=None, channelList=None, xmin=None, xmax=None, minvalue=None, maxvalue=None, figuretitle=None, save=False, gpath='./'):
@@ -118,9 +142,9 class Figure:
118 142 self.drvObj.driver.closePage()
119 143 self.__isFigureOpen = False
120 144
121
145 self.selectFigure()
122 146 self.__initFigure()
123
147
124 148 for channel in channelList:
125 149 frameObj = self.frameObjList[channel]
126 150 frameObj.init(xmin=self.xmin,
@@ -218,6 +242,7 class Figure:
218 242 self.deltay,
219 243 self.colorbar,
220 244 value)
245 self.selectFigure()
221 246
222 247 for channel in channelList:
223 248 dataCh = data[channel,:]
@@ -232,13 +257,20 class Figure:
232 257
233 258
234 259 class Frame:
260
261 drvObj = None
262 idFrame = None
235 263 nplots = None
236 264 plotObjList = []
237 265 title = ""
238 266
239 267 def __init__(self,drvObj, idframe):
268
240 269 self.drvObj = drvObj
241 270 self.idframe = idframe
271 nplots = None
272 self.plotObjList = []
273
242 274 self.createPlots()
243 275
244 276 def createPlots(self):
@@ -270,6 +302,15 class Frame:
270 302
271 303
272 304 class Plot:
305
306 drvObj = None
307 idframe = None
308 idplot = None
309 xi = None
310 yi = None
311 xw = None
312 yw = None
313
273 314 title = ""
274 315 xlabel = ""
275 316 ylabel = ""
@@ -287,6 +328,7 class Plot:
287 328 cbypos = None
288 329
289 330 def __init__(self, drvObj, idframe, idplot, xi, yi, xw, yw):
331
290 332 self.drvObj = drvObj
291 333 self.idframe = idframe
292 334 self.idplot = idplot
@@ -43,6 +43,9 class PlplotDriver:
43 43 plplot.plparseopts([self.wintitle],plplot.PL_PARSE_FULL)
44 44 plplot.plsetopt("geometry", "%dx%d"%(self.xw, self.yw))
45 45
46 def selectFigure(self):
47
48 plplot.plsstrm(self.idfigure)
46 49
47 50 def openDriver(self, pldriver=None):
48 51 if pldriver == None:
@@ -421,7 +424,7 class PlplotDriver:
421 424 rgb_lvl = [8,8,4] #Levels for RGB colors
422 425
423 426 if ncolor == None:
424 raise ValueError, "The colormap selected is not valid"
427 raise ValueError, "The colormap selected (%s) is not valid" %(colormap)
425 428
426 429 plplot.plscmap1n(ncolor)
427 430 plplot.plscmap1l(1, pos, r, g, b)
@@ -254,7 +254,7 class JRODataReader(JRODataIO):
254 254 pass
255 255
256 256 else:
257 print "Searching files in offline mode"
257 print "Searching files in offline mode ..."
258 258 pathList, filenameList = self.__searchFilesOffLine(path, startDate, endDate, startTime, endTime, set, expLabel, ext)
259 259
260 260 if not(pathList):
@@ -367,7 +367,7 class SpectraReader(JRODataReader):
367 367
368 368 self.dataOutObj.channelIndexList = range(self.systemHeaderObj.nChannels)
369 369
370 self.dataOutObj.dataUtcTime = self.basicHeaderObj.utc + self.basicHeaderObj.miliSecond/1000.#+ self.profileIndex * self.ippSeconds
370 self.dataOutObj.utctime = self.basicHeaderObj.utc + self.basicHeaderObj.miliSecond/1000.#+ self.profileIndex * self.ippSeconds
371 371
372 372 self.dataOutObj.flagShiftFFT = self.processingHeaderObj.shif_fft
373 373
@@ -695,8 +695,8 class SpectraWriter(JRODataWriter):
695 695 self.basicHeaderObj.version = self.versionFile
696 696 self.basicHeaderObj.dataBlock = self.nTotalBlocks
697 697
698 utc = numpy.floor(self.dataOutObj.dataUtcTime)
699 milisecond = (self.dataOutObj.dataUtcTime - utc)* 1000.0
698 utc = numpy.floor(self.dataOutObj.utctime)
699 milisecond = (self.dataOutObj.utctime - utc)* 1000.0
700 700
701 701 self.basicHeaderObj.utc = utc
702 702 self.basicHeaderObj.miliSecond = milisecond
@@ -293,7 +293,7 class VoltageReader(JRODataReader):
293 293
294 294 self.dataOutObj.flagTimeBlock = self.flagTimeBlock
295 295
296 self.dataOutObj.dataUtcTime = self.basicHeaderObj.utc + self.basicHeaderObj.miliSecond/1000. + self.profileIndex * self.ippSeconds
296 self.dataOutObj.utctime = self.basicHeaderObj.utc + self.basicHeaderObj.miliSecond/1000. + self.profileIndex * self.ippSeconds
297 297
298 298 self.dataOutObj.nCohInt = self.processingHeaderObj.nCohInt
299 299
@@ -526,8 +526,8 class VoltageWriter(JRODataWriter):
526 526 self.basicHeaderObj.version = self.versionFile
527 527 self.basicHeaderObj.dataBlock = self.nTotalBlocks
528 528
529 utc = numpy.floor(self.dataOutObj.dataUtcTime)
530 milisecond = (self.dataOutObj.dataUtcTime - utc)* 1000.0
529 utc = numpy.floor(self.dataOutObj.utctime)
530 milisecond = (self.dataOutObj.utctime - utc)* 1000.0
531 531
532 532 self.basicHeaderObj.utc = utc
533 533 self.basicHeaderObj.miliSecond = milisecond
@@ -11,7 +11,7 import datetime
11 11 path = os.path.split(os.getcwd())[0]
12 12 sys.path.append(path)
13 13
14 from Data.JROData import SpectraHeis
14 from Data.JROData import Spectra, SpectraHeis
15 15 from IO.SpectraIO import SpectraWriter
16 16 from Graphics.schainPlotTypes import ScopeFigure, SpcFigure
17 17 #from JRONoise import Noise
@@ -48,8 +48,8 class SpectraProcessor:
48 48 self.plotObjIndex = None
49 49 self.integratorOst = []
50 50 self.plotObjList = []
51 self.noiseObj = bjList = []
52 self.writerObjLiNone
51 self.noiseObj = []
52 self.writerObjList = []
53 53 self.buffer = None
54 54 self.profIndex = 0
55 55
@@ -59,10 +59,24 class SpectraProcessor:
59 59 raise ValueError, "This SpectraProcessor.setup() function needs dataInObj input variable"
60 60
61 61 if dataInObj.type == "Voltage":
62 if nFFTPoints == None:
62 if nFFTPoints == None:
63 63 raise ValueError, "This SpectraProcessor.setup() function needs nFFTPoints input variable"
64 else:
64
65
66
67 if dataInObj.type == "Spectra":
68 if nFFTPoints != None:
69 raise ValueError, "The nFFTPoints cannot be selected to this object type"
70
65 71 nFFTPoints = dataInObj.nFFTPoints
72
73 if pairList == None:
74 pairList = self.dataInObj.pairList
75
76 if pairList == None:
77 nPairs = 0
78 else:
79 nPairs = len(pairList)
66 80
67 81 self.dataInObj = dataInObj
68 82
@@ -70,44 +84,55 class SpectraProcessor:
70 84 dataOutObj = Spectra()
71 85
72 86 self.dataOutObj = dataOutObj
87 self.dataOutObj.nFFTPoints = nFFTPoints
88 self.dataOutObj.pairList = pairList
89 self.dataOutObj.nPairs = nPairs
73 90
74 91 return self.dataOutObj
75 92
76 93 def init(self):
77 94
95 self.dataOutObj.flagNoData = True
96
97 if self.dataInObj.flagNoData:
98 return 0
99
78 100 self.integratorObjIndex = 0
79 101 self.writerObjIndex = 0
80 102 self.plotObjIndex = 0
103
104
105 if self.dataInObj.type == "Spectra":
106
107 self.dataOutObj.copy(self.dataInObj)
108 self.dataOutObj.flagNoData = False
109 return
110
81 111 if self.dataInObj.type == "Voltage":
82 112
83 113 if self.buffer == None:
84 self.buffer = numpy.zeros((self.nChannels,
85 self.nFFTPoints,
114 self.buffer = numpy.zeros((self.dataInObj.nChannels,
115 self.dataOutObj.nFFTPoints,
86 116 self.dataInObj.nHeights),
87 117 dtype='complex')
88 118
89 119 self.buffer[:,self.profIndex,:] = self.dataInObj.data
90 120 self.profIndex += 1
91 121
92 if self.profIndex == self.nFFTPoints:
122 if self.profIndex == self.dataOutObj.nFFTPoints:
123
124 self.__updateObjFromInput()
93 125 self.__getFft()
126
94 127 self.dataOutObj.flagNoData = False
95 128
96 129 self.buffer = None
97 130 self.profIndex = 0
98 return
99 131
100 self.dataOutObj.flagNoData = True
101
102 return
103
104 #Other kind of data
105 if self.dataInObj.type == "Spectra":
106 self.dataOutObj.copy(self.dataInObj)
107 self.dataOutObj.flagNoData = False
108 132 return
109 133
110 raise ValueError, "The dtype is not valid"
134 #Other kind of data
135 raise ValueError, "The type object %(s) is not valid " %(self.dataOutObj.type)
111 136
112 137 def __getFft(self):
113 138 """
@@ -136,9 +161,6 class SpectraProcessor:
136 161 self.dataOutObj.m_ProcessingHeader.spectraComb
137 162 self.dataOutObj.m_ProcessingHeader.shif_fft
138 163 """
139
140 if self.dataInObj.flagNoData:
141 return 0
142 164
143 165 fft_volt = numpy.fft.fft(self.buffer,axis=1)
144 166 dc = fft_volt[:,0,:]
@@ -154,9 +176,9 class SpectraProcessor:
154 176
155 177 cspc = None
156 178 pairIndex = 0
157 if self.pairList != None:
179 if self.dataOutObj.pairList != None:
158 180 #calculo de cross-spectra
159 cspc = numpy.zeros((self.nPairs, self.nFFTPoints, self.nHeights), dtype='complex')
181 cspc = numpy.zeros((self.dataOutObj.nPairs, self.dataOutObj.nFFTPoints, self.dataOutObj.nHeights), dtype='complex')
160 182 for pair in self.pairList:
161 183 cspc[pairIndex,:,:] = numpy.abs(fft_volt[pair[0],:,:] * numpy.conjugate(fft_volt[pair[1],:,:]))
162 184 pairIndex += 1
@@ -165,12 +187,33 class SpectraProcessor:
165 187 self.dataOutObj.data_spc = spc
166 188 self.dataOutObj.data_cspc = cspc
167 189 self.dataOutObj.data_dc = dc
168 self.dataOutObj.m_ProcessingHeader.blockSize = blocksize
169 self.dataOutObj.m_BasicHeader.utc = self.dataInObj.m_BasicHeader.utc
190 self.dataOutObj.blockSize = blocksize
170 191
171 192 # self.getNoise()
193
194 def __updateObjFromInput(self):
195
196 self.dataOutObj.radarControllerHeaderObj = self.dataInObj.radarControllerHeaderObj.copy()
197 self.dataOutObj.systemHeaderObj = self.dataInObj.systemHeaderObj.copy()
198 self.dataOutObj.channelList = self.dataInObj.channelList
199 self.dataOutObj.heightList = self.dataInObj.heightList
200 self.dataOutObj.dtype = self.dataInObj.dtype
201 self.dataOutObj.nHeights = self.dataInObj.nHeights
202 self.dataOutObj.nChannels = self.dataInObj.nChannels
203 self.dataOutObj.nBaud = self.dataInObj.nBaud
204 self.dataOutObj.nCode = self.dataInObj.nCode
205 self.dataOutObj.code = self.dataInObj.code
206 self.dataOutObj.nProfiles = self.dataOutObj.nFFTPoints
207 self.dataOutObj.channelIndexList = self.dataInObj.channelIndexList
208 self.dataOutObj.flagTimeBlock = self.dataInObj.flagTimeBlock
209 self.dataOutObj.utctime = self.dataInObj.utctime
210 self.dataOutObj.flagDecodeData = self.dataInObj.flagDecodeData #asumo q la data esta decodificada
211 self.dataOutObj.flagDeflipData = self.dataInObj.flagDeflipData #asumo q la data esta sin flip
212 self.dataOutObj.flagShiftFFT = self.dataInObj.flagShiftFFT
213 self.dataOutObj.nIncohInt = 1
172 214
173 215 def addWriter(self, wrpath, blocksPerFile):
216
174 217 objWriter = SpectraWriter(self.dataOutObj)
175 218 objWriter.setup(wrpath, blocksPerFile)
176 219 self.writerObjList.append(objWriter)
@@ -181,6 +224,7 class SpectraProcessor:
181 224 self.integratorObjList.append(objIncohInt)
182 225
183 226 def addSpc(self, idfigure, nframes, wintitle, driver, colormap, colorbar, showprofile):
227
184 228 spcObj = SpcFigure(idfigure, nframes, wintitle, driver, colormap, colorbar, showprofile)
185 229 self.plotObjList.append(spcObj)
186 230
@@ -193,16 +237,20 class SpectraProcessor:
193 237 maxvalue=None,
194 238 wintitle='',
195 239 driver='plplot',
196 colormap='br_greeen',
240 colormap='br_green',
197 241 colorbar=True,
198 242 showprofile=False,
199 243 save=False,
200 gpath=None):
244 gpath=None,
245 channelList = None):
201 246
202 247 if self.dataOutObj.flagNoData:
203 248 return 0
204 249
205 nframes = len(self.dataOutObj.channelList)
250 if channelList == None:
251 channelList = self.dataOutObj.channelList
252
253 nframes = len(channelList)
206 254
207 255 if len(self.plotObjList) <= self.plotObjIndex:
208 256 self.addSpc(idfigure, nframes, wintitle, driver, colormap, colorbar, showprofile)
@@ -211,8 +259,6 class SpectraProcessor:
211 259
212 260 y = self.dataOutObj.heightList
213 261
214 channelList = self.dataOutObj.channelList
215
216 262 data = 10.*numpy.log10(self.dataOutObj.data_spc[channelList,:,:])
217 263 # noisedB = 10.*numpy.log10(noise)
218 264 noisedB = numpy.arange(len(channelList)+1)
@@ -222,7 +268,7 class SpectraProcessor:
222 268 title = "%.2f"%noisedB[i]
223 269 titleList.append(title)
224 270
225 thisdatetime = datetime.datetime.fromtimestamp(self.dataOutObj.dataUtcTime)
271 thisdatetime = datetime.datetime.fromtimestamp(self.dataOutObj.utctime)
226 272 dateTime = "%s"%(thisdatetime.strftime("%d-%b-%Y %H:%M:%S"))
227 273 figuretitle = "Spc Radar Data: %s"%dateTime
228 274
@@ -251,8 +297,9 class SpectraProcessor:
251 297
252 298
253 299 def writeData(self, wrpath, blocksPerFile):
300
254 301 if self.dataOutObj.flagNoData:
255 return 0
302 return 0
256 303
257 304 if len(self.writerObjList) <= self.writerObjIndex:
258 305 self.addWriter(wrpath, blocksPerFile)
@@ -313,13 +360,19 class SpectraHeisProcessor:
313 360 return self.dataOutObj
314 361
315 362 def init(self):
363
364 self.dataOutObj.flagNoData = True
365
366 if self.dataInObj.flagNoData:
367 return 0
368
316 369 self.integratorObjIndex = 0
317 370 self.writerObjIndex = 0
318 371 self.plotObjIndex = 0
319 372
320 373 if self.dataInObj.type == "Voltage":
374 self.__updateObjFromInput()
321 375 self.__getFft()
322 self.__updateFromObj()
323 376 self.dataOutObj.flagNoData = False
324 377 return
325 378
@@ -331,7 +384,8 class SpectraHeisProcessor:
331 384
332 385 raise ValueError, "The type is not valid"
333 386
334 def __updateFromObj(self):
387 def __updateObjFromInput(self):
388
335 389 self.dataOutObj.radarControllerHeaderObj = self.dataInObj.radarControllerHeaderObj.copy()
336 390 self.dataOutObj.systemHeaderObj = self.dataInObj.systemHeaderObj.copy()
337 391 self.dataOutObj.channelList = self.dataInObj.channelList
@@ -347,15 +401,13 class SpectraHeisProcessor:
347 401 self.dataOutObj.channelIndexList = self.dataInObj.channelIndexList
348 402 self.dataOutObj.flagNoData = self.dataInObj.flagNoData
349 403 self.dataOutObj.flagTimeBlock = self.dataInObj.flagTimeBlock
350 self.dataOutObj.dataUtcTime = self.dataInObj.dataUtcTime
404 self.dataOutObj.utctime = self.dataInObj.utctime
351 405 self.dataOutObj.flagDecodeData = self.dataInObj.flagDecodeData #asumo q la data esta decodificada
352 406 self.dataOutObj.flagDeflipData = self.dataInObj.flagDeflipData #asumo q la data esta sin flip
353 407 self.dataOutObj.flagShiftFFT = self.dataInObj.flagShiftFFT
354 408 self.dataOutObj.nIncohInt = 1
355 409
356 410 def __getFft(self):
357 if self.dataInObj.flagNoData:
358 return 0
359 411
360 412 fft_volt = numpy.fft.fft(self.dataInObj.data, axis=1)
361 413 #print fft_volt
@@ -375,10 +427,12 class SpectraHeisProcessor:
375 427 return numpy.arange(int(self.nFFTPoints))
376 428
377 429 def addIntegrator(self,N,timeInterval):
430
378 431 objIncohInt = IncoherentIntegration(N,timeInterval)
379 432 self.integratorObjList.append(objIncohInt)
380 433
381 434 def integrator(self, N=None, timeInterval=None):
435
382 436 if self.dataOutObj.flagNoData:
383 437 return 0
384 438
@@ -386,7 +440,7 class SpectraHeisProcessor:
386 440 self.addIntegrator(N,timeInterval)
387 441
388 442 myIncohIntObj = self.integratorObjList[self.integratorObjIndex]
389 myIncohIntObj.exe(data=self.dataOutObj.data_spc,timeOfData=self.dataOutObj.dataUtcTime)
443 myIncohIntObj.exe(data=self.dataOutObj.data_spc,timeOfData=self.dataOutObj.utctime)
390 444
391 445 if myIncohIntObj.isReady:
392 446 self.dataOutObj.data_spc = myIncohIntObj.data
@@ -403,6 +457,7 class SpectraHeisProcessor:
403 457
404 458
405 459 def addScope(self, idfigure, nframes, wintitle, driver):
460
406 461 if idfigure==None:
407 462 idfigure = self.plotObjIndex
408 463
@@ -436,7 +491,7 class SpectraHeisProcessor:
436 491
437 492 x = numpy.arange(self.dataOutObj.nHeights)
438 493
439 thisDatetime = datetime.datetime.fromtimestamp(self.dataOutObj.dataUtcTime)
494 thisDatetime = datetime.datetime.fromtimestamp(self.dataOutObj.utctime)
440 495
441 496 dateTime = "%s"%(thisDatetime.strftime("%d-%b-%Y %H:%M:%S"))
442 497 date = "%s"%(thisDatetime.strftime("%d-%b-%Y"))
@@ -85,15 +85,15 class VoltageProcessor:
85 85 data = self.dataOutObj.data * numpy.conjugate(self.dataOutObj.data)
86 86 data = 10*numpy.log10(data.real)
87 87
88 # currenttime = self.dataOutObj.dataUtcTime
88 # currenttime = self.dataOutObj.utctime
89 89 # if timezone == "lt":
90 currenttime = self.dataOutObj.dataUtcTime - time.timezone
90 currenttime = self.dataOutObj.utctime - time.timezone
91 91
92 92 range = self.dataOutObj.heightList
93 93
94 94 channelList = self.dataOutObj.channelList
95 95
96 thisdatetime = datetime.datetime.fromtimestamp(self.dataOutObj.dataUtcTime)
96 thisdatetime = datetime.datetime.fromtimestamp(self.dataOutObj.utctime)
97 97 dateTime = "%s"%(thisdatetime.strftime("%d-%b-%Y %H:%M:%S"))
98 98 date = "%s"%(thisdatetime.strftime("%d-%b-%Y"))
99 99
@@ -159,7 +159,7 class VoltageProcessor:
159 159 if type =="iq":
160 160 data1D = self.dataOutObj.data
161 161
162 thisDatetime = datetime.datetime.fromtimestamp(self.dataOutObj.dataUtcTime)
162 thisDatetime = datetime.datetime.fromtimestamp(self.dataOutObj.utctime)
163 163
164 164 dateTime = "%s"%(thisDatetime.strftime("%d-%b-%Y %H:%M:%S"))
165 165 date = "%s"%(thisDatetime.strftime("%d-%b-%Y"))
General Comments 0
You need to be logged in to leave comments. Login now