1
2 """
3 Frequency Analysis Routines.
4
5 Here are some examples:
6
7 Section 1. Pulsation frequency analysis
8 =======================================
9
10 Do a frequency analysis of the star HD129929, after Aerts 2004. We reproduce
11 their Fig. 8 here.
12
13 Import necessary modules:
14
15 >>> from ivs.catalogs import vizier
16
17 Read in the data and do an iterative prewhitening frequency analysis:
18
19 >>> data,units,comms = vizier.search('J/A+A/415/241/table1')
20 >>> params = iterative_prewhitening(data.HJD,data.Umag-data.Umag.mean(),f0=6.2,fn=7.2,maxiter=6,\
21 stopcrit=(stopcrit_scargle_snr,4.,6,))
22 >>> print pl.mlab.rec2txt(params,precision=6)
23 const ampl freq phase e_const e_ampl e_freq e_phase stopcrit
24 0.000310 0.014722 6.461700 0.443450 0.000401 0.001323 0.000006 0.089865 4.950496
25 0.000000 0.014866 6.978306 -0.050189 0.000000 0.001224 0.000006 0.082305 5.677022
26 0.000000 0.011687 6.449591 0.016647 0.000000 0.001090 0.000007 0.093280 5.747565
27 0.000000 0.011546 6.990431 -0.482814 0.000000 0.001001 0.000006 0.086678 6.454955
28 0.000000 0.009380 6.590938 -0.382048 0.000000 0.000938 0.000007 0.100016 6.510729
29 0.000000 0.007645 6.966174 0.323627 0.000000 0.000863 0.000008 0.112876 5.703801
30
31 Plot the results:
32
33 >>> p = pl.figure()
34 >>> p = pl.vlines(params['freq'],0,params['ampl'],color='k',linestyle='-')
35 >>> p = pl.xlabel('Frequency [c d$^{-1}$]')
36 >>> p = pl.ylabel('Amplitude [magnitude]')
37 >>> p = pl.annotate('$\ell=2$',(6.46,0.015),xycoords='data',va='bottom',ha='center',fontsize='large')
38 >>> p = pl.annotate('$\ell=0$',(6.59,0.010),xycoords='data',va='bottom',ha='center',fontsize='large')
39 >>> p = pl.annotate('$\ell=1$',(6.99,0.015),xycoords='data',va='bottom',ha='center',fontsize='large')
40 >>> p = pl.xlim(6.2,7.2)
41 >>> p = pl.ylim(0,0.018)
42
43
44 Section 2: Line profile variability
45 ===================================
46
47 We generate some line profiles as Gaussian profiles, with varying average. This
48 as an analogue for purely radial variability.
49
50 >>> wave = np.linspace(4950.,5000.,200.)
51 >>> times = np.linspace(0,1.,100)
52 >>> A,sigma,mu = 0.5, 5., 4975.
53 >>> frequency = 1/0.2
54 >>> wshifts = 10.*np.sin(2*np.pi*frequency*times)
55 >>> spectra = np.zeros((len(times),len(wave)))
56 >>> for i,wshift in enumerate(wshifts):
57 ... spectra[i] = 1.-evaluate.gauss(wave,[A,mu+wshift,sigma])+np.random.normal(size=len(wave),scale=0.01)
58
59 Once the line profiles are constructed, we can compute the Fourier transform
60 of these lines:
61
62 >>> output = spectrum_2D(times,wave,spectra,f0=frequency/2.,fn=3*frequency,df=0.001,threads=2,method='scargle',subs_av=True,full_output=True)
63
64 With this output, we can find retrieve the frequency. We plot the periodograms
65 for each wavelength bin on the left, and on the right the average over all
66 wavelength bins:
67
68 >>> p = pl.figure()
69 >>> p = pl.subplot(121)
70 >>> p = pl.imshow(output['pergram'][1][::-1],extent=[wave[0],wave[-1],frequency/2,3*frequency],aspect='auto')
71 >>> p = pl.xlabel('Wavelength (A)')
72 >>> p = pl.ylabel('Frequency (c/d)')
73 >>> cbar = pl.colorbar()
74 >>> cbar.set_label('Amplitude')
75 >>> p = pl.subplot(122)
76 >>> p = pl.plot(output['pergram'][1].mean(axis=1),output['pergram'][0],'k-')
77 >>> p = pl.ylim(frequency/2,3*frequency)
78 >>> p = pl.xlabel('Amplitude')
79 >>> p = pl.ylabel('Frequency (c/d)')
80
81 ]]include figure]]timeseries_freqanalyse_02.png]
82
83 We can then fix the frequency and compute all the parameters. However, we now
84 choose not to subtract the average profile, but instead use the GLS periodogram
85 to include the constant
86
87 >>> output = spectrum_2D(times,wave,spectra,f0=frequency-0.05,fn=frequency+0.05,df=0.001,threads=2,method='gls',subs_av=False,full_output=True)
88
89 >>> p = pl.figure()
90 >>> p = pl.subplot(221)
91 >>> p = pl.errorbar(wave,output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-')
92 >>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Constant')
93 >>> p = pl.subplot(222)
94 >>> p = pl.errorbar(wave,output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-')
95 >>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Amplitude')
96 >>> p = pl.subplot(223)
97 >>> p = pl.errorbar(wave,output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-')
98 >>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Frequency (c/d)')
99 >>> p = pl.subplot(224)
100 >>> p = pl.errorbar(wave,output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-')
101 >>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Phase')
102
103 ]]include figure]]timeseries_freqanalyse_03.png]
104
105 Section 3. Time-frequency analysis
106 ==================================
107
108 Example usage: we generate some data where the frequency jumps to double
109 its value in the middle of the time series. The first half is thus given by
110
111 >>> params = np.rec.fromarrays([[10.],[1.],[0.],[0.]],names=['freq','ampl','const','phase'])
112 >>> times = np.linspace(0,15,1000)
113 >>> signal = evaluate.sine(times,params)
114
115 And the second half by
116
117 >>> params['freq'] = 20.
118 >>> signal[:len(times)/2] = evaluate.sine(times[:len(times)/2],params)
119
120 The time-frequency analysis is calculate via the command
121
122 >>> output = time_frequency(times,signal,fn=30.)
123
124 And the outcome can be plotted via
125
126 >>> p = pl.figure()
127 >>> p = pl.imshow(output['pergram'][1].T[::-1],aspect='auto',extent=[times[0],times[-1],output['pergram'][0][0],output['pergram'][0][-1]])
128 >>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Frequency (c/d)')
129
130 ]]include figure]]timeseries_freqanalyse_04.png]
131
132 or
133
134 >>> p = pl.figure()
135 >>> p = pl.subplot(221)
136 >>> p = pl.errorbar(output['times'],output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-')
137 >>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Constant')
138 >>> p = pl.subplot(222)
139 >>> p = pl.errorbar(output['times'],output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-')
140 >>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Amplitude')
141 >>> p = pl.subplot(223)
142 >>> p = pl.errorbar(output['times'],output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-')
143 >>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Frequency (c/d)')
144 >>> p = pl.subplot(224)
145 >>> p = pl.errorbar(output['times'],output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-')
146 >>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Phase')
147
148 ]]include figure]]timeseries_freqanalyse_05.png]
149
150 Author: Pieter Degroote
151 """
152 import logging
153 import numpy as np
154 import pylab as pl
155 from ivs.sigproc import fit
156 from ivs.sigproc import evaluate
157 from ivs.timeseries import pergrams
158 from ivs.timeseries.decorators import defaults_pergram
159 from ivs.aux import numpy_ext
160 import os
161
162 logger = logging.getLogger("TS.FREQANAL")
163
164
165
166 -def find_frequency(times,signal,method='scargle',model='sine',full_output=False,
167 optimize=0,max_loops=20, scale_region=0.1, scale_df=0.20, model_kwargs=None,
168 correlation_correction=True,prewhiteningorder_snr=False,
169 prewhiteningorder_snr_window=1.,**kwargs):
170 """
171 Find one frequency, automatically going to maximum precision and return
172 parameters & error estimates.
173
174 This routine makes the frequency grid finer until it is finer than the
175 estimated error on the frequency. After that, it will compute (harmonic)
176 parameters and estimate errors.
177
178 There is a possibility to escape this optimization by setting scale_df=0 or
179 freqregscale=0.
180
181 You can include a nonlinear least square update of the parameters, by
182 setting the keyword C{optimize=1} (optimization outside loop) or
183 C{optimize=2} (optimization after each iteration).
184
185 The method with which to find the frequency can be set with the keyword
186 C{method}, the model used to fit and optimize should be set with C{model}.
187 Extra keywords for the model functions should go in C{model_kwargs}. If
188 C{method} is a tuple, the first method will be used for the first frequency
189 search only. This could be useful to take advantage of such methods as
190 fasper which do not allow for iterative zoom-ins. By default, the function looks
191 for the highest (or deepest in the case of the pdm method) peak, but instead it is
192 possible to go for the peak having the highest SNR before prewhitening by setting
193 C{prewhiteningorder_snr} to True. In this case, the noise spectrum is calculated
194 using a convolution with a C{prewhiteningorder_snr_window} wide box.
195
196 Possible extra keywords: see definition of the used periodogram function.
197
198 B{Warning}: the timeseries must be B{sorted in time} and B{cannot contain
199 the same timepoint twice}. Otherwise, a 'ValueError, concatenation problem'
200 can occur.
201
202 Example keywords:
203 - 'correlation_correction', default=True
204 - 'freqregscale', default=0.5: factor for zooming in on frequency
205 - 'dfscale', default = 0.25: factor for optimizing frequency resolution
206
207 Example usage: We generate a sine signal
208
209 >>> times = np.linspace(0,100,1000)
210 >>> signal = np.sin(2*np.pi*2.5*times) + np.random.normal(size=len(times))
211
212 Compute the frequency
213
214 >>> parameters, pgram, model = find_frequency(times,signal,full_output=True)
215
216 Make a plot:
217
218 >>> p = pl.figure()
219 >>> p = pl.axes([0.1,0.3,0.85,0.65])
220 >>> p = pl.plot(pgram[0],pgram[1],'k-')
221 >>> p = pl.xlim(2.2,2.8)
222 >>> p = pl.ylabel('Amplitude')
223 >>> p = pl.axes([0.1,0.1,0.85,0.2])
224 >>> p = pl.plot(pgram[0][:-1],np.diff(pgram[0]),'k-')
225 >>> p = pl.xlim(2.2,2.8)
226 >>> p,q = pl.xlabel('Frequency (c/d)'),pl.ylabel('Frequency resolution $\Delta F$')
227
228 ]]include figure]]timeseries_freqanalyse_06.png]
229
230 @rtype: record array(, 2x1Darray, 1Darray)
231 @return: parameters and errors(, periodogram, model function)
232 """
233 if model_kwargs is None:
234 model_kwargs = dict()
235
236 e_f = 0
237 freq_diff = np.inf
238 prev_freq = -np.inf
239 counter = 0
240
241 f_max = np.inf
242 f_min = 0.
243
244
245
246 method_kwargs = kwargs.copy()
247
248 while freq_diff>e_f/10.:
249
250
251
252 if freq_diff==np.inf and not isinstance(method,str):
253 method_ = method[1]
254 method = method[0]
255
256 freqs,ampls = getattr(pergrams,method)(times,signal,**method_kwargs)
257 f0,fn,df = freqs[0],freqs[-1],freqs[1]-freqs[0]
258
259 if freq_diff==np.inf and not isinstance(method,str):
260 method = method_
261
262
263 if method in ['pdm']:
264 frequency = freqs[np.argmin(ampls)]
265
266 if prewhiteningorder_snr:
267 if counter == 0:
268 windowlength = float(prewhiteningorder_snr_window)/(freqs[1]-freqs[0])
269 window = np.ones(int(windowlength))/float(windowlength)
270 ampls_ = np.concatenate((ampls[::-1],ampls,ampls[::-1]))
271 noises_ = np.convolve(ampls_, window, 'same')
272 noises = np.split(noises_,3)[1]
273 freqs_old = np.copy(freqs)
274 noises_old = np.copy(noises)
275 else:
276 noises = np.interp(freqs,freqs_old,noises_old)
277 frequency = freqs[np.argmax(ampls/noises)]
278 else:
279 frequency = freqs[np.argmax(ampls)]
280 if full_output and counter==0:
281 freqs_,ampls_ = freqs,ampls
282
283 params = getattr(fit,model)(times,signal,frequency,**model_kwargs)
284 if hasattr(fit,'e_'+model):
285 errors = getattr(fit,'e_'+model)(times,signal,params,correlation_correction=correlation_correction)
286 e_f = errors['e_freq'][-1]
287
288 else:
289 errors = None
290
291
292 if optimize==2:
293 params_,errors_,gain = fit.optimize(times,signal,params,model)
294 if gain>0:
295 params = params_
296 logger.info('Accepted optimization (gained %g%%)'%gain)
297
298
299 freq_diff = abs(frequency-prev_freq)
300 prev_freq = frequency
301 freq_region = fn-f0
302 f0 = max(f_min,frequency-freq_region*scale_region/2.)
303 fn = min(f_max,frequency+freq_region*scale_region/2.)
304 df *= scale_df
305 method_kwargs['f0'] = f0
306 method_kwargs['fn'] = fn
307 method_kwargs['df'] = df
308
309
310 if scale_region==0 or scale_df==0:
311 break
312 if counter >= max_loops:
313 logger.error("Frequency precision not reached in %d steps, breaking loop"%(max_loops))
314 break
315 if (fn-f0)/df<5:
316 logger.error("Frequency precision not reached with stepsize %e , breaking loop"%(df/scale_df))
317 break
318 counter += 1
319
320 if optimize==1:
321 params_,errors_,gain = fit.optimize(times,signal,params,model)
322 if gain>0:
323 params = params_
324 logger.info('Accepted optimization (gained %g%%)'%gain)
325
326 if errors is not None:
327 params = numpy_ext.recarr_join(params,errors)
328 logger.info("%s model parameters via %s periodogram:\n"%(model,method)+pl.mlab.rec2txt(params,precision=8))
329
330
331 if full_output:
332 mymodel = getattr(evaluate,model)(times,params)
333 return params,(freqs_,ampls_),mymodel
334 else:
335 return params
336
337
338
339 -def iterative_prewhitening(times,signal,maxiter=1000,optimize=0,method='scargle',
340 model='sine',full_output=False,stopcrit=None,correlation_correction=True,
341 prewhiteningorder_snr=False,prewhiteningorder_snr_window=1.,**kwargs):
342 """
343 Fit one or more functions to a timeseries via iterative prewhitening.
344
345 This function will use C{find_frequency} to fit the function parameters to
346 the original signal (including any previously found parameters),
347 optimize the parameters if needed, and remove it from the data to search
348 for a new frequency in the residuals.
349
350 It is always the original signal that is used to fit all parameters again;
351 B{only the (optimized) frequency is remembered from step to step} (Vanicek's
352 method).
353
354 You best set C{maxiter} to some sensable value, to hard-limit the number of
355 frequencies that will be searched for. You can additionally use a C{stopcrit}
356 and stop looking for frequencies once it is reached. C{stopcrit} should be
357 a tuple; the first argument is the function to call, the other arguments
358 are passed to the function, after the mandatory arguments C{times,signal,
359 modelfunc,allparams,pergram}. See L{stopcrit_scargle_snr} for an example of
360 such a function.
361
362 By default, the function looks for the highest (or deepest in the case of the pdm
363 method) peak, but instead it is possible to go for the peak having the highest
364 SNR before prewhitening by setting C{prewhiteningorder_snr} to True. In this case,
365 the noise spectrum is calculated using a convolution with a
366 C{prewhiteningorder_snr_window} wide box. Usage of this is strongly encouraged,
367 especially combined with L{stopcrit_scargle_snr} as C{stopcrit}.
368
369 @return: parameters, model(, model function)
370 @rtype: rec array(, ndarray)
371 """
372 residuals = signal.copy()
373 frequencies = []
374 stop_criteria = []
375 while maxiter:
376
377 params,pergram,this_fit = find_frequency(times,residuals,method=method,
378 full_output=True,correlation_correction=correlation_correction,
379 prewhiteningorder_snr=prewhiteningorder_snr,
380 prewhiteningorder_snr_window=prewhiteningorder_snr_window,**kwargs)
381
382
383 frequencies.append(params['freq'][-1])
384 allparams = getattr(fit,model)(times,signal,frequencies)
385
386
387 if optimize>0:
388 residuals_for_optimization = residuals
389 if optimize<=len(params):
390 model_fixed_params = getattr(evaluate,model)(times,allparams[:-optimize])
391 residuals_for_optimization -= model_fixed_params
392 uparams,e_uparams, gain = fit.optimize(times,residuals_for_optimization,allparams[-optimize:],model)
393
394 if gain>0:
395 allparams[-optimize:] = uparams
396 logger.info('Accepted optimization (gained %g%%)'%gain)
397
398
399 modelfunc = getattr(evaluate,model)(times,allparams)
400 residuals = signal - modelfunc
401
402
403 maxiter -= 1
404
405
406 if stopcrit is not None:
407 func = stopcrit[0]
408 args = stopcrit[1:]
409 condition,value = func(times,signal,modelfunc,allparams,pergram,*args)
410 logger.info('Stop criterion (%s): %.3g'%(func.__name__,value))
411 stop_criteria.append(value)
412 if condition:
413 logger.info('Stop criterion reached')
414 break
415
416
417 e_allparams = getattr(fit,'e_'+model)(times,signal,allparams,correlation_correction=correlation_correction)
418
419 allparams = numpy_ext.recarr_join(allparams,e_allparams)
420 if stopcrit is not None:
421 allparams = numpy_ext.recarr_join(allparams,np.rec.fromarrays([stop_criteria],names=['stopcrit']))
422
423 if full_output:
424 return allparams,modelfunc
425 else:
426 return allparams
427
428
429
430
431 -def spectrum_2D(x,y,matrix,weights_2d=None,show_progress=False,
432 subs_av=True,full_output=False,**kwargs):
433 """
434 Compute a 2D periodogram.
435
436 Rows (first axis) should be spectra chronologically
437
438 x are time points (length N)
439 y are second axis units (e.g. wavelengths) (length NxM)
440
441 If the periodogram/wavelength combination has a large range, this script
442 can produce a B{ValueError} or B{MemoryError}. To solve this, you could
443 iterate this function over a subset of wavelength bins yourself, and write
444 the results to a file.
445
446 This function also outputs a model, which you can use to subtract from the
447 data (probably together with the average profile, which is also returned)
448 and look further for any frequencies::
449
450 output = spectrum_2D(x,y,matrix,subs_av=True)
451 new_matrix = matrix - output['avprof'] - output['model'
452 output2 = spectrum_2D(x,y,new_matrix,subs_av=False)
453
454 If you want to prewhiten a model, you'd probably want to use the same
455 frequency across the whole line profile. You can hack this by setting
456 C{f0=frequency} and C{fn=frequency+df} with C{df} the size of the frequency
457 step.
458
459 B{Example usage}: first we generate some variable line profiles. In this case,
460 this is just a radial velocity variation
461
462 >>> times = np.linspace(0,150,100)
463 >>> wavel = np.r_[4500:4600:1.0]
464 >>> matrix = np.zeros((len(times),len(wavel)))
465 >>> for i in xrange(len(times)):
466 ... central_wave = 5*np.sin(2*np.pi/10*times[i])
467 ... matrix[i,:] = 1 - 0.5*np.exp( -(wavel-4550-central_wave)**2/10**2)
468
469 Once the line profiles are constructed, we can compute the Fourier transform
470 of these lines:
471
472 >>> output = spectrum_2D(times,wavel,matrix,method='scargle',model='sine',f0=0.05,fn=0.3,subs_av=True,full_output=True)
473
474 With this output, we can find retrieve the frequency. We plot the periodograms
475 for each wavelength bin on the left, and on the right the average over all
476 wavelength bins:
477
478 >>> p = pl.figure()
479 >>> p = pl.subplot(121)
480 >>> p = pl.imshow(output['pergram'][1][::-1],extent=[wavel[0],wavel[-1],0.05,0.3],aspect='auto')
481 >>> p = pl.subplot(122)
482 >>> p = pl.plot(output['pergram'][1].mean(axis=1),output['pergram'][0],'k-')
483 >>> p = pl.ylim(0.05,0.3)
484
485 We can then fix the frequency and compute all the parameters. However, we now
486 choose not to subtract the average profile, but instead use the GLS periodogram
487 to include the constant
488
489 >>> output = spectrum_2D(times,wavel,matrix,method='gls',model='sine',f0=0.095,fn=0.105,full_output=True)
490
491 >>> p = pl.figure()
492 >>> p = pl.subplot(221)
493 >>> p = pl.errorbar(wavel,output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-')
494 >>> p = pl.subplot(222)
495 >>> p = pl.errorbar(wavel,output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-')
496 >>> p = pl.subplot(223)
497 >>> p = pl.errorbar(wavel,output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-')
498 >>> p = pl.subplot(224)
499 >>> p = pl.errorbar(wavel,output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-')
500
501 @return: dict with keys C{avprof} (2D array), C{pars} (rec array), C{model} (1D array), C{pergram} (freqs,2Darray)
502 @rtype: dict
503 """
504
505 if subs_av:
506 matrix_av = np.outer(np.ones(len(matrix)),matrix.mean(axis=0))
507 matrix = matrix - matrix_av
508 else:
509 matrix_av = 0.
510
511
512 params = []
513 freq_spectrum = []
514 mymodel = []
515
516 for iwave in xrange(len(matrix[0])):
517 signal = matrix[:,iwave]
518 if weights_2d is not None:
519 weights = weights_2d[:,iwave]
520 kwargs['weights'] = weights
521
522
523
524 out = find_frequency(x,signal,full_output=full_output,scale_df=0,**kwargs)
525
526
527 if full_output:
528 params.append(out[0])
529 freq_spectrum.append(out[1][1])
530 mymodel.append(out[2])
531 else:
532 params.append(out)
533
534
535 output = {}
536 output['avprof'] = matrix_av
537 output['pars'] = np.hstack(params)
538 if full_output:
539 output['pergram'] = out[1][0],np.vstack(freq_spectrum).T
540 output['model'] = np.vstack(mymodel).T
541
542 return output
543
544 @defaults_pergram
545 -def time_frequency(times,signal,window_width=None,n_windows=100,
546 window='rectangular',detrend=None,**kwargs):
547 """
548 Short Time (Fourier) Transform.
549
550 Slide a window through the timeseries, multiply the timeseries with the
551 window, and perform a Fourier Transform.
552
553 It is best to fix explicitly C{f0}, C{fn} and C{df}, to limit the computation
554 time!
555
556 Extra kwargs go to L{find_frequency}
557
558 @param n_windows: number of slices
559 @type n_windows: integer
560 @param window_width: width of each slice (defaults to T/20)
561 @type window_width: float
562 @param detrend: detrending function, accepting times and signal as args
563 @type detrend: callable
564 @param window: window function to apply
565 @type window: string
566 @return: spectrogram, times used, parameters, errors, points used per slice
567 @rtype: dict
568 """
569 if window_width is None:
570 window_width = kwargs.get('window_width',times.ptp()/20.)
571
572 stft_times = np.linspace(times[0]+window_width/2.,times[-1]-window_width/2.,n_windows)
573
574
575
576 f0 = kwargs.pop('f0')
577 fn = kwargs.pop('fn')
578 df = kwargs.pop('df')
579 nyq_stat = kwargs.pop('nyq_stat',fn)
580
581
582 pars = []
583 pnts = np.zeros(n_windows)
584 spec = None
585
586 for i,t in enumerate(stft_times):
587 region = (abs(times-t) <= (window_width/2.))
588 times_ = times[region]
589 signal_ = signal[region]
590 if detrend:
591 times_,signal_ = detrend(times_,signal_)
592 pnts[i] = len(times_)
593 if len(times_)>1:
594 output = find_frequency(times_,signal_,full_output=True,f0=f0,fn=fn,df=df,nyq_stat=nyq_stat,scale_df=0,**kwargs)
595 if spec is None:
596 spec = np.ones((n_windows,len(output[1][1])))
597 pars.append(output[0])
598 spec[i,:len(output[1][1])] = output[1][1]
599 else:
600 nanpars = np.rec.array(np.nan*np.ones(len(pars[-1].dtype.names)),dtype=pars[-1].dtype)
601 pars.append(nanpars)
602
603
604
605
606
607
608
609
610
611 spec[i] = np.nan
612 out = {}
613 out['times'] = stft_times
614 out['pars'] = np.hstack(pars)
615 out['pergram'] = (output[1][0],spec)
616 return out
617
621 """
622 Stop criterium based on probability.
623 """
624 value = pergrams.scargle_probability(pergram[1].max(),times,pergram[0])
625 print value
626 return value>crit_value,value
627
629 """
630 Stop criterium based on signal-to-noise ratio.
631 """
632 width = width/2.
633 argmax = np.argmax(pergram[1])
634 ampls = pergram[1]
635 start = max(0,pergram[0][argmax]-width)
636 stop = min(pergram[0][argmax]+width,pergram[0][-1])
637 if start==0:
638 stop += width-pergram[0][argmax]
639 if stop==pergram[0][-1]:
640 start = pergram[0][-1]-pergram[0][argmax]+width
641 ampls = ampls[(start<=pergram[0]) & (pergram[0]<=stop)]
642 value = pergram[1][argmax]/ampls.mean()
643 return value<crit_value,value
644
645
646 -def autocorrelation(frequencies,power,max_step=1.5,interval=(),threshold=None,method=1):
647 """
648 Compute the autocorrelation.
649
650 The formulate to estimate the autocorrelation in the periodogram is
651
652 R(k) = 1 / (n * s^2) * \sum_{t=1}^n (X_t - mu) * (X_{t+k} - mu)
653
654 where n is the number of points in the interval, mu an estimator for the
655 sample mean and s is an estimator for the standard deviation of the sample.
656
657 When computed over a large interval, we have to take into account that
658 we cannot sample more points than we have in the spectrum, so we have to
659 average out over fewer and fewer points, the more we shift the spectrum.
660
661 Above formula becomes
662
663 R(k) = 1 / ((n-k) * s^2) * \sum_{t=1}^{n-k} (X_t - mu) * (X_{t+k} - mu)
664
665 Highs and lows are cut of. The lower cut off value is the sample mean, the
666 higher cut off is a user-defined multiple of the sample mean.
667
668 This function can also be used to compute period spacings: just invert the
669 frequency spacing, reverse the order and reinterpolate to be equidistant:
670 Example:
671 >>> periods = linspace(1./p[0][-1],1./p[0][0],2*len(p[0]))
672 >>> ampl = interpol.linear_interpolation(1./p[0][::-1],p[1][::-1],periods)
673 >>> ampl = where(isnan(ampl),hstack([ampl[1:],ampl[:1]]),ampl)
674
675 @param frequencies: frequency array
676 @type frequencies: numpy 1d array
677 @param power: power spectrum
678 @type power: numpy 1d array
679 @keyword max_step: maximum time shift
680 @type max_step: float
681 @keyword interval: tuple of frequencies (start, end)
682 @type interval: tuple of floats
683 @keyword threshold: high cut off value (in units of sample mean)
684 @type threshold: float
685 @return: domain of autocorrelation and autocorrelation
686 @rtype: (ndarray,ndarray)
687 """
688
689 if interval is not ():
690 cut_out = frequencies[(interval[0]<=frequencies) & (frequencies<=interval[1])]
691 start_freq = cut_out[0]
692 stop_freq = cut_out[-1]
693 start = np.argmin(abs(frequencies-interval[0]))
694 stop = np.argmin(abs(frequencies-interval[1]))
695 else:
696 start = 1
697 stop = len(frequencies)-1
698
699
700 Dfreq = (frequencies[start+1] - frequencies[start+0])
701 max_step = int(max_step/Dfreq)
702 autocorr = []
703 variance = []
704 mean = np.average(power)
705
706
707
708 if threshold is not None:
709 power[power>=(threshold*mean)] = threshold*mean
710
711
712
713 mean = np.average(power)
714 power = power-mean
715
716
717
718 for i in xrange(2,max_step):
719 end_s = min(len(power), stop+i)
720 end_o = start + end_s - (start+i)
721 original = power[start :end_o]
722 shifted = power[start+i:end_s]
723 if len(original) < 10:
724 logger.error("AUTOCORR: too few points left in interval, breaking up.")
725 break
726 if method==1:
727 autocorr.append(np.average(original*shifted))
728 else:
729 autocorr.append(np.correlate(original,shifted))
730 variance.append(np.average(original*original))
731
732 domain = np.arange(2,max_step) * Dfreq
733 domain = domain[0:len(autocorr)]
734
735
736 autocorr = np.array(autocorr)/np.array(variance)
737 logger.info("Computed autocorrelation in interval %s with maxstep %s"%(interval,max_step))
738 return domain, autocorr
739
740 if __name__=="__main__":
741 import doctest
742 import pylab as pl
743 import sys
744 from ivs.aux import argkwargparser
745 from ivs.inout import ascii
746
747
748 if not sys.argv[1:]:
749 doctest.testmod()
750 pl.show()
751 sys.exit()
752
753
754
755
756
757
758 else:
759 method,args,kwargs = argkwargparser.parse()
760 print "Running method %s with arguments %s and keyword arguments %s"%(method,args,kwargs)
761 if '--help' in args or 'help' in args or 'help' in kwargs:
762 sys.exit()
763 full_output = kwargs.get('full_output',False)
764 times,signal = ascii.read2array(kwargs.pop('infile')).T[:2]
765 out = globals()[method](times,signal, **kwargs)
766
767
768 if method=='find_frequency' and full_output:
769 print pl.mlab.rec2txt(out[0],precision=8)
770 pl.figure()
771 pl.subplot(211)
772 pl.plot(out[1][0],out[1][1],'k-')
773 pl.subplot(212)
774 pl.plot(times,signal,'ko',ms=2)
775 pl.plot(times,out[2],'r-',lw=2)
776 pl.show()
777 elif method=='find_frequency':
778 print pl.mlab.rec2txt(out)
779
780
781 elif method=='time_frequency':
782 print pl.mlab.rec2txt(out['pars'],precision=8)
783 pl.figure()
784 pl.imshow(out['pergram'][1].T[::-1],aspect='auto',extent=[out['times'][0],out['times'][-1],out['pergram'][0][0],out['pergram'][0][-1]])
785
786
787 pl.show()
788