def fktrafo(stream, normalize=True): """ Calculates the f,k - transformation of the data in stream. Returns the trafo as an array. :param st: Stream :type st: obspy.core.stream.Stream :param inv: inventory :type inv: obspy.station.inventory.Inventory :param event: Event :type event: obspy.core.event.Event returns :param fkdata: f,k - transformation of data in stream :type fkdata: numpyndarray """ st_tmp = stream.copy() ArrayData = stream2array(st_tmp, normalize) ix = ArrayData.shape[0] iK = int(math.pow(2,nextpow2(ix))) it = ArrayData.shape[1] iF = int(math.pow(2,nextpow2(it))) fkdata = np.fft.fft2(ArrayData, s=(iK,iF)) return fkdata
def ifktrafo(fkdata, stream, normalize=True): """ Calculates the inverse f,k - transformation of the data in fkdata. Returns the trafo as an array. """ StreamData= stream2array(stream) ix = StreamData.shape[0] iK = int(math.pow(2,nextpow2(ix))) it = StreamData.shape[1] iF = int(math.pow(2,nextpow2(it))) fk_tmp = fkdata.copy() ArrayData = np.fft.ifft2(fkdata, s=(iK,iF)) ArrayData = ArrayData[0:ix, 0:it] return ArrayData
def pocs(data, maxiter, noft, alpha=0.9, beta=None, method='linear', dmethod='denoise', peaks=None, maskshape=None, dt=None, p=None, flow=None, fhigh=None, slidingwindow=False, overlap=0.5): """ This functions reconstructs missing signals in the f-k domain, using the original data, including gaps, filled with zeros. It applies the projection onto convex sets (pocs) algorithm in 2D. Reference: 3D interpolation of irregular data with a POCS algorithm, Abma & Kabir, 2006 :param data: :type data: :param maxiter: :type maxiter: :param nol: Number of loops :type nol: :param alpha: Factor of threshold decrease after each iteration :type alpha: float :param method: Method to be used for the pocs algorithm -'linear', 'exp', 'mask' or 'ssa' :param peaks: Slope values for the mask :param maskshape: Shape of the corners of mask, see makemask returns: :param datap: :type datap: """ #if not decrease in ('linear', 'exp', None): # msg='No decrease method chosen' # raise IOError(msg) ArrayData = data.copy() ix = ArrayData.shape[0] iK = int(math.pow(2,nextpow2(ix))) it = ArrayData.shape[1] iF = int(math.pow(2,nextpow2(it))) fkdata = np.fft.fft2(ArrayData, s=(iK,iF)) threshold = abs(fkdata.max()) ADold = ArrayData.copy() ADnew = ArrayData.copy() ADfinal = np.zeros(ArrayData.shape).astype('complex') if method in ('linear', 'exp'): if slidingwindow: if dmethod in ('reconstruct'): w_length = int(data.shape[1] / 3.) swh = np.hanning(w_length) loc = 0. inside = True while inside: curr_win = int(loc) ADtemp = ArrayData[:,curr_win:curr_win+w_length].copy() for i in range(maxiter): data_tmp = ADtemp.copy() fkdata = np.fft.fft2(data_tmp, s=(iK,iF)) fkdata[ np.where(abs(fkdata) < threshold)] = 0. + 0j if method in ('linear'): threshold = threshold * alpha elif method in ('exp'): threshold = threshold * sp.exp(-(i+1) * alpha) data_tmp = np.fft.ifft2(fkdata, s=(iK,iF)).real[0:ix, 0:it].copy() ADtemp[noft] = data_tmp[noft][:,curr_win:curr_win+w_length].copy() if loc == 0.: ADfinal[:,curr_win:curr_win+w_length] = ADtemp.copy() else: ADfinal[:,curr_win:curr_win+w_length] = ADfinal[:,curr_win:curr_win+w_length] + ADtemp ADfinal[:,curr_win-int(overlap*w_length):int(curr_win)] = ( ADold[:,int((1-overlap)*w_length):] + ADtemp[:,:int(overlap*w_length)] ) / 2. ADold = ADtemp.copy() threshold = abs(np.fft.fft2(ADold, s=(iK,iF)).max()) loc += overlap * w_length print(loc) if loc >= data.shape[1]: inside=False else: if dmethod in ('reconstruct'): ADtemp = ArrayData.copy() for i in range(maxiter): data_tmp = ADtemp.copy() fkdata = np.fft.fft2(data_tmp, s=(iK,iF)) fkdata[ np.where(abs(fkdata) < threshold)] = 0. + 0j if method in ('linear'): threshold = threshold * alpha elif method in ('exp'): threshold = threshold * sp.exp(-(i+1) * alpha) data_tmp = np.fft.ifft2(fkdata, s=(iK,iF)).real[0:ix, 0:it].copy() ADtemp[noft] = data_tmp[noft] name = str(i) + '.png' # plt.ion() # plotfk(fkdata) # fig = plt.gcf() # fig.set_size_inches(7,8) # fig.savefig(name) # plt.ioff() ADfinal = ADtemp.copy() threshold = abs(np.fft.fft2(ADfinal, s=(iK,iF)).max()) elif dmethod in ('denoise', 'de-noise'): for n in noft: ADtemp = ArrayData.copy() for i in range(maxiter): data_tmp = ADtemp.copy() fkdata = np.fft.fft2(data_tmp, s=(iK,iF)) fkdata[ np.where(abs(fkdata) < threshold)] = 0. + 0j if method in ('linear'): threshold = threshold * alpha elif method in ('exp'): threshold = threshold * sp.exp(-(i+1) * alpha) data_tmp = np.fft.ifft2(fkdata, s=(iK,iF)).real[0:ix, 0:it].copy() ADtemp[n] = data_tmp[n] #save = 'pocsdata' + str(i) + '.png' #plot(data_tmp, ylabel='Distance(m)', xlabel='Time(s)', fs=22, yinfo=2, savefig=save) ADfinal = ADtemp.copy() threshold = abs(np.fft.fft2(ADfinal, s=(iK,iF)).max()) elif method in ('mask'): W = makeMask(fkdata, peaks[0], maskshape) ADfinal = ArrayData.copy() for n in noft: ADtemp = ArrayData.copy() threshold = abs(W*np.fft.fft2(ADfinal, s=(iK,iF))).max() for i in range(maxiter): data_tmp =ADtemp.copy() fkdata = W * np.fft.fft2(data_tmp, s=(iK,iF)) fkdata[ np.where(abs(fkdata) < threshold)] = 0. + 0j threshold = threshold * alpha data_tmp = np.fft.ifft2(fkdata, s=(iK,iF)).real[0:ix, 0:it].copy() ADtemp[n] = data_tmp[n] ADfinal[n] = ADtemp[n].copy() elif method in ('ssa'): for n in noft: for i in range(maxiter): data_tmp = ArrayData.copy() data_ssa = fx_ssa(data_tmp,dt,p,flow,fhigh) ArrayData = alpha * ArrayData ArrayData[n] = (1. - alpha) * data_ssa[n] ADfinal[n] = ArrayData[n].copy() elif method in ('average'): threshold = beta * abs(np.fft.fft2(ArrayData, s=(iK,iF)).max()) ADtemp = ArrayData.copy() for n in noft: for i in range(maxiter): data_tmp = ADtemp.copy() fkdata = np.fft.fft2(data_tmp, s=(iK,iF)) fkdata[ np.where(abs(fkdata) < threshold)] = 0. + 0j ADtemp = alpha*data_tmp + (1. - alpha) * np.fft.ifft2(fkdata, s=(iK,iF)).real[0:ix, 0:it] ADtemp[n] = (1. - alpha) * np.fft.ifft2(fkdata, s=(iK,iF)).real[0:ix, 0:it][n] ADfinal = ADtemp.copy() elif method == 'maskvary': ADfinal = ArrayData.copy() ADtemp = ArrayData.copy() for n in noft: for i in range(maxiter): W = makeMask(fkdata, peaks, shape=maskshape, expl_cutoff=i) data_tmp = ADtemp.copy() fkdata = W * np.fft.fft2(data_tmp, s=(iK,iF)) data_tmp = np.fft.ifft2(fkdata, s=(iK,iF)).real[0:ix, 0:it].copy() ADtemp[n] = alpha * ArrayData[n].copy() ADtemp[n] += (1. - alpha) * data_tmp[n] ADfinal[n] = ArrayData[n].copy() else: print('no method specified') return datap = ADfinal.copy() return datap
def fk_filter(st, inv=None, event=None, ftype='eliminate', fshape=['butterworth', 4, 4], phase=None, polygon=4, normalize=True, stack=False, slopes=[-3,3], deltaslope=0.05, slopepicking=False, smoothpicks=False, dist=0.5, maskshape=['boxcar',None], order=4., peakinput=False, eval_mean=1, fs=25): """ Import stream, the function applies an 2D FFT, removes a certain window around the desired phase to surpress a slownessvalue corresponding to a wavenumber and applies an 2d iFFT. To fill the gap between uneven distributed stations use array_util.gaps_fill_zeros(). A method to interpolate the signals in the fk-domain is beeing build, also a method using a norm minimization method. Alternative is an nonequidistant 2D Lombard-Scargle transformation. param st: Stream type st: obspy.core.stream.Stream param inv: inventory type inv: obspy.station.inventory.Inventory param event: Event type event: obspy.core.event.Event param ftype: type of method, default is 'eliminate-polygon', possible inputs are: -eliminate -extract -eliminate-polygon -extract-polygon -mask -fk type ftype: string param fshape: fshape[0] describes the shape of the fk-filter in case of ftype is 'eliminate' or 'extract'. Possible inputs are: -spike (default) -boxcar -taper -butterworth fshape[1] is an additional attribute to the shape of taper and butterworth, for: -taper: fshape[1] = slope of sides -butterworth: fshape[1] = number of poles fshape[3] describes the length of the filter shape, respectivly wavenumber corner points around k=0, e.g.: fshape['taper', 2, 4] produces a symmetric taper with slope of side = 2, where the signal is reduced about 50% at k=+-2 type fshape: list param phase: name of the phase to be investigated type phase: string param polygon: number of vertices of polygon for fk filter, only needed if ftype is set to eliminate-polygon or extract-polygon. Default is 12. type polygon: int param normalize: normalize data to 1 type normalize: bool param SSA: Force SSA algorithm or let it check, default:False type SSA: bool param eval_mean: number of linear events used to calculate the average of the area in the fk domain. returns: stream_filtered, the filtered stream. References: Yilmaz, Thomas Author: S. Schneider 2016 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details: http://www.gnu.org/licenses/ """ # Convert format and prepare Variables. # Check for Data type of variables. if not type(st ) == Stream: print( "Wrong input type of stream, must be obspy.core.stream.Stream" ) raise TypeError if len(fshape) == 1: fshape = [fshape[0], None, None] st_tmp = st.copy() ArrayData = stream2array(st_tmp, normalize) ix = ArrayData.shape[0] iK = int(math.pow(2,nextpow2(ix))) try: yinfo = epidist2nparray(attach_epidist2coords(inv, event, st_tmp)) dx = (yinfo.max() - yinfo.min() + 1) / yinfo.size k_axis = np.fft.fftfreq(iK, dx) except: try: ymax = st_tmp[0].stats.distance ymin = st_tmp[0].stats.distance for trace in st_tmp: if trace.stats.distance > ymax: ymax = trace.stats.distance if trace.stats.distance < ymin: ymin = trace.stats.distance dx = (ymax - ymin + 1) / len(st_tmp) k_axis = np.fft.fftfreq(iK, dx) except: print("\nNo inventory or event-information found. \nContinue without specific distance and wavenumber information.") yinfo=None dx=None k_axis=None it = ArrayData.shape[1] iF = int(math.pow(2,nextpow2(it))) dt = st_tmp[0].stats.delta f_axis = np.fft.fftfreq(iF,dt) # Calc mean diff of each epidist entry if it is reasonable # do a partial stack and apply filter. """ 2D Frequency-Space / Wavenumber-Frequency Filter ######################################################### """ # 2D f-k Transformation # Note array_fk has f on the x-axis and k on the y-axis!!! # For interaction the conj.-transposed Array is shown!!! # Decide when to use SSA to fill the gaps, calc mean distance of each epidist entry # if it differs too much --> SSA if ftype in ("eliminate"): if phase: if not isinstance(event, Event) and not isinstance(inv, Inventory): msg='For alignment on phase calculation inventory and event information is needed, not found.' raise IOError(msg) st_al = alignon(st_tmp, inv, event, phase) ArrayData = stream2array(st_al, normalize) array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) array_filtered_fk = line_set_zero(array_fk, shape=fshape) else: array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) array_filtered_fk = line_set_zero(array_fk, shape=fshape) elif ftype in ("extract"): if phase: if not isinstance(event, Event) and not isinstance(inv, Inventory): msg='For alignment on phase calculation inventory and event information is needed, not found.' raise IOError(msg) st_al = alignon(st_tmp, inv, event, phase) ArrayData = stream2array(st_al, normalize) array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) array_filtered_fk = line_cut(array_fk, shape=fshape) else: array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) array_filtered_fk = line_cut(array_fk, shape=fshape) elif ftype in ("eliminate-polygon"): array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) if phase: if not isinstance(event, Event) and not isinstance(inv, Inventory): msg='For alignment on phase calculation inventory and event information is needed, not found.' raise IOError(msg) st_al = alignon(st_tmp, inv, event, phase) ArrayData = stream2array(st_al, normalize) array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) array_filtered_fk = _fk_eliminate_polygon(array_fk, polygon, ylabel=r'frequency domain f in Hz', \ yticks=f_axis, xlabel=r'wavenumber domain k in $\frac{1}{^{\circ}}$', xticks=k_axis, eval_mean=eval_mean, fs=fs) else: array_filtered_fk = _fk_eliminate_polygon(array_fk, polygon, ylabel=r'frequency domain f in Hz', \ yticks=f_axis, xlabel=r'wavenumber domain k in $\frac{1}{^{\circ}}$', xticks=k_axis, eval_mean=eval_mean, fs=fs) elif ftype in ("extract-polygon"): array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) if phase: if not isinstance(event, Event) and not isinstance(inv, Inventory): msg='For alignment on phase calculation inventory and event information is needed, not found.' raise IOError(msg) st_al = alignon(st_tmp, inv, event, phase) ArrayData = stream2array(st_al, normalize) array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) array_filtered_fk = _fk_extract_polygon(array_fk, polygon, ylabel=r'frequency domain f in Hz', \ yticks=f_axis, xlabel=r'wavenumber domain k in $\frac{1}{^{\circ}}$', xticks=k_axis, eval_mean=eval_mean, fs=fs) else: array_filtered_fk = _fk_extract_polygon(array_fk, polygon, ylabel=r'frequency domain f in Hz', \ yticks=f_axis, xlabel=r'wavenumber domain k in $\frac{1}{^{\circ}}$', xticks=k_axis, eval_mean=eval_mean, fs=fs) elif ftype in ("mask"): array_fk = np.fft.fft2(ArrayData) M, prange, peaks = slope_distribution(array_fk, slopes, deltaslope, peakpick=None, mindist=dist, smoothing=smoothpicks, interactive=slopepicking) W = makeMask(array_fk, peaks[0], maskshape) array_filtered_fk = array_fk * W array_filtered = np.fft.ifft2(array_filtered_fk) stream_filtered = array2stream(array_filtered, st_original=st.copy()) return stream_filtered, array_fk, W elif ftype in ("fk"): if phase: if not isinstance(event, Event) and not isinstance(inv, Inventory): msg='For alignment on phase calculation inventory and event information is needed, not found.' raise IOError(msg) st_al = alignon(st_tmp, inv, event, phase) ArrayData = stream2array(st_al, normalize) array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) ### BUILD DOUBLE TAPER ### #array_filtered_fk = else: array_fk = np.fft.fft2(ArrayData, s=(iK,iF)) ### BUILD DOUBLE TAPER ### #array_filtered_fk = else: print("No type of filter specified") raise TypeError array_filtered = np.fft.ifft2(array_filtered_fk, s=(iK,iF)).real # Convert to Stream object. array_filtered = array_filtered[0:ix, 0:it] stream_filtered = array2stream(array_filtered, st_original=st.copy()) return stream_filtered
def radon_inverse(st, inv, event, p, weights, line_model, inversion_model, hyperparameters): """ This function inverts move-out data to the Radon domain given the inputs: :param st: :param inv: :param event: :param p: -- vector of slowness axis you would like to invert to. :param weights: -- weighting vector that determines importance of each trace. set vector to ones for no preference. :param line_model: select one of the following options for path integration: 'linear' - linear paths in the spatial domain (default) 'parabolic' - parabolic paths in the spatial domain. :param inversion model: select one of the following options for regularization schema: 'L2' - Regularized on the L2 norm of the Radon domain (default) 'L1' - Non-linear regularization based on L1 norm and iterative reweighted least sqaures (IRLS) see Sacchi 1997. 'Cauchy' - Non-linear regularization see Sacchi & Ulrych 1995 :param hyperparameters: trades-off between fitting the data and chosen damping. returns: radon domain is ordered size(R)==[length(p),length(t)], time-axis and distance-axis. Known limitations: - Assumes evenly sampled time axis. - Assumes move-out data isn't complex. References: Schultz, R., Gu, Y. J., 2012. Flexible Matlab implementation of the Radon Transform. Computers and Geosciences. An, Y., Gu, Y. J., Sacchi, M., 2007. Imaging mantle discontinuities using least-squares Radon transform. Journal of Geophysical Research 112, B10303. Author: R. Schultz, 2012 Translated to Python by: S. Schneider, 2016 """ # Check for Data type of variables. if not isinstance(st, Stream) or not isinstance(inv, Inventory) or not isinstance(event, Event): msg = "Wrong input type must be obspy Stream, Inventory and Event" raise TypeError if not isinstance(hyperparameters,list): msg = "Wrong input type of mu, must be list" raise TypeError # Define some array/matrices lengths. st_tmp = st.copy() M = stream2array(st_tmp) epi = epidist2nparray(attach_epidist2coords(inv, event, st_tmp)) delta = np.array([ epi.copy() ]) ref_dist = np.mean(delta) if not weights: weights = np.ones(delta.size) t = np.linspace(0,st_tmp[0].stats.delta * st_tmp[0].stats.npts, st_tmp[0].stats.npts) it=t.size print(it) iF=int(math.pow(2,nextpow2(it)+1)) # Double length iDelta=delta.size ip=len(p) iw=len(weights) #Exit if inconsistent data is input. if M.shape != (iDelta, it): print("Dimensions inconsistent!\nShape of M is not equal to (len(delta),len(t)) \nShape of M = (%i , %i)\n(len(delta),len(t)) = (%i, %i) \n" % (M.shape[0], M.shape[1], iDelta, it) ) R=0 return(R) if iw != iDelta: print("Dimensions inconsistent!\nlen(delta) ~= len(weights)\nlen(delta) = %i\nlen(weights) = %i\n" % (iDelta, iw)) R=0 return(R) #Exit if improper hyperparameters are entered. if inversion_model in ["L1", "Cauchy"]: if not len(hyperparameters == 2): print("Improper number of trade-off parameters\n") R=0 return(R) else: #The code's default is L2 inversion. if not len(hyperparameters) == 1: print("Improper number of trade-off parameters\n") R=0 return(R) #Preallocate space in memory. R=np.zeros((ip,it)) Rfft=np.zeros((ip,iF)) + 0j A=np.zeros((iDelta,ip)) + 0j Tshift=np.zeros((iDelta,ip)) + 0j AtA=np.zeros((ip,ip)) + 0j AtM=np.zeros((ip,1)) + 0j Ident=np.identity(ip) #Define some values Dist_array=delta-ref_dist dF=1./(t[0]-t[1]) Mfft=np.fft.fft(M,iF,1) W=sparse.spdiags(weights.conj().transpose(), 0, iDelta, iDelta).A dCOST=0. COST_curv=0. COST_prev=0. #Populate ray parameter then distance data in time shift matrix. for j in range(iDelta): if line_model == "parabolic": Tshift[j]=p else: #Linear is default Tshift[j]=p for k in range(ip): if line_model == 'parabolic': Tshift[:,k]=(2. * ref_dist * Tshift[:,k] * Dist_array.conj().transpose()) + (Tshift[:,k] * (Dist_array**2).conj().transpose()) else: #Linear is default Tshift[:,k]=Tshift[:,k] * Dist_array[0].conj().transpose() # Loop through each frequency. for i in range( int(math.floor((iF+1)/2)) ): print('Step %i of %i' % (i, int(math.floor((iF+1)/2))) ) # Make time-shift matrix, A. f = ((float(i)/float(iF))*dF) A = np.exp( (0.+1j)*2*pi*f * Tshift ) # M = A R ---> AtM = AtA R # Solve the weighted, L2 least-squares problem for an initial solution. AtA = dot( dot(A.conj().transpose(), W), A ) AtM = dot( A.conj().transpose(), dot( W, Mfft[:,i] ) ) mu = abs(np.trace(AtA)) * hyperparameters[0] Rfft[:,i] = sp.linalg.solve((AtA + mu*Ident), AtM) #Non-linear methods use IRLS to solve, iterate until convergence to solution. if inversion_model in ("Cauchy", "L1"): #Initialize hyperparameters. b=hyperparameters[1] lam=mu*b #Initialize cost functions. dCOST = float("Inf") if inversion_model == "Cauchy": COST_prev = np.linalg.norm( Mfft[:,i] - dot(A,Rfft[:,i]), 2 ) + lam*sum( np.log( abs(Rfft[:,i]**2 + b) ) ) elif inversion_model == "L1": COST_prev = np.linalg.norm( Mfft[:,i] - dot(A,Rfft[:,i]), 2 ) + lam*np.linalg.norm( abs(Rfft[:,i]+1), 1 ) itercount=1 #Iterate until negligible change to cost function. while dCost > 0.001 and itercount < 10: #Setup inverse problem. if inversion_model == "Cauchy": Q = sparse.spdiags( 1./( abs(Rfft[:,i]**2) + b), 0, ip, ip).A elif inversion_model == "L1": Q = sparse.spdiags( 1./( abs(Rfft[:,i]) + b), 0, ip, ip).A Rfft[:,i]=sp.linalg.solve( ( lam * Q + AtA ), AtM ) #Determine change to cost function. if inversion_model == "Cauchy": COST_cur = np.linalg.norm( Mfft[:,i]-A*Rfft[:,i], 2 ) + lam*sum( np.log( abs(Rfft[:,i]**2 + b )-np.log(b) ) ) elif inversion_model == "L1": COST_cur = np.linalg.norm( Mfft[:,i]-A*Rfft[:,i], 2 ) + lam*np.linalg.norm( abs(Rfft[:,i]+1) + b, 1 ) dCOST = 2*abs(COST_cur - COST_prev)/(abs(COST_cur) + abs(COST_prev)) COST_prev = COST_cur itercount += 1 #Assuming Hermitian symmetry of the fft make negative frequencies the complex conjugate of current solution. if i != 0: Rfft[:,iF-i] = Rfft[:,i].conjugate() R = np.fft.ifft(Rfft, iF) R = R[:,0:it] return R, t, epi
def radon_forward(t,p,R,delta,ref_dist,line_model): """ This function applies the time-shift Radon operator A, to the Radon domain. Will calculate the move-out data, given the inputs: -t -- vector of time axis. -p -- vector of slowness axis you would like to invert to. -R -- matrix of Radon data, ordered size(R)==[length(p),length(t)]. -delta -- vector of distance axis. -ref_dist -- reference distance the path-function will shift about. -line_model, select one of the following options for path integration: 'linear' - linear paths in the spatial domain (default) 'parabolic' - parabolic paths in the spatial domain. Output spatial domain is ordered size(M)==[length(delta),length(t)]. Known limitations: - Assumes evenly sampled time axis. - Assumes Radon data isn't complex. References: Schultz, R., Gu, Y. J., 2012. Flexible Matlab implementation of the Radon Transform. Computers and Geosciences [In Preparation] An, Y., Gu, Y. J., Sacchi, M., 2007. Imaging mantle discontinuities using least-squares Radon transform. Journal of Geophysical Research 112, B10303. Author: R. Schultz, 2012 Translated to Python by: S. Schneider, 2016 """ # Check for Data type of variables. if not isinstance(t, numpy.ndarray) or not isinstance(delta, numpy.ndarray): print( "Wrong input type of t or delta, must be numpy.ndarray" ) raise TypeError it=t.size iF=int(math.pow(2,nextpow2(it)+1)) # Double length iDelta=delta.size ip=len(p) #Exit if inconsistent data is input. if R.shape != (ip, it): print("Dimensions inconsistent!\nShape of M is not equal to (len(delta),len(t)) \nShape of M = (%i , %i)\n(len(delta),len(t)) = (%i, %i) \n" % (M.shape[0], M.shape[1], iDelta, it) ) M=0 return(M) #Preallocate space in memory. Mfft = np.zeros((iDelta, iF)) + 0j A = np.zeros((iDelta, ip)) + 0j Tshift = np.zeros((iDelta, ip)) + 0j #Define some values. Dist_array=delta-ref_dist dF=1./(t[0]-t[1]) Rfft=np.fft.fft(R,iF,1) #Populate ray parameter then distance data in time shift matrix. for j in range(iDelta): if line_model == "parabolic": Tshift[j,:]=p else: #Linear is default Tshift[j,:]=p for k in range(ip): if line_model == 'parabolic': Tshift[:,k]=(2. * ref_dist * Tshift[:,k] * Dist_array.conj().transpose()) + (Tshift[:,k] * (Dist_array**2).conj().transpose()) else: #Linear is default Tshift[:,k]=Tshift[:,k] * Dist_array.conj().transpose() # Loop through each frequency. for i in range( int(math.floor((iF+1)/2))-1 ): # Make time-shift matrix, A. f = ((float(i)/float(iF))*dF) A = np.exp( (0.+1j)*2*pi*f * Tshift ) # Apply Radon operator. Mfft[:,i]=dot(A, Rfft[:,i]) # Assuming Hermitian symmetry of the fft make negative frequencies the complex conjugate of current solution. if i != 0: Mfft[:,iF-i] = Mfft[:,i].conjugate() M = np.fft.ifft(Mfft, iF) M = M[:,0:it] return(M)