这个提交包含在:
葛峻恺
2025-06-17 15:55:18 +00:00
提交者 Gitee
父节点 2a37219281
当前提交 98698d7bce
共有 2 个文件被更改,包括 559 次插入0 次删除

129
load_main.py 普通文件
查看文件

@@ -0,0 +1,129 @@
import segyio
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import tukey, hilbert
import mig_fk # Custom FK migration module
def write_to_dat_append(data, x, y, file):
"""
Append 3D point cloud data to a .dat file in the format (x, y, z, value)
Parameters:
data (2D array): Processed data to export
x (float): X offset for the current profile
y (float): Y offset for the current profile
file (file object): Output file opened in write/append mode
"""
nz, nx = data.shape
x_coords = np.linspace(x + 8, 213, nx)
z_coords = np.linspace(0, 85, nz)
for i in range(nz):
for j in range(nx):
file.write(f"{x_coords[j]} {y + 3} {z_coords[i]} {data[i, j]}\n")
def sliding_window_mean(data, window_size):
"""
Compute local background mean using a sliding window
Parameters:
data (2D array): Input data of shape (n_traces, n_samples)
window_size (int): Size of the sliding window (must be odd)
Returns:
2D array: Local mean values with the same shape as input
"""
if window_size % 2 == 0:
raise ValueError("Window size must be an odd number.")
half_window = window_size // 2
padded_data = np.pad(data, ((0, 0), (half_window, half_window)), mode='reflect')
local_means = np.empty_like(data)
for i in range(data.shape[1]):
local_means[:, i] = np.mean(padded_data[:, i:i + window_size], axis=1)
return local_means
def taper_field(in_array):
"""
Apply a Tukey window-based taper to suppress edge noise
Parameters:
in_array (1D array): Input radar trace
Returns:
1D array: Tapered trace
"""
window = tukey(500, 0.3)
window[65:] = 1 # Force central part to remain unchanged
window = window ** 2
return in_array * window
def load(filename, static, trace_remove, x, y, sub_aver, amplify):
"""
Load and process GPR data from a SEG-Y file
Parameters:
filename (str): Path to SEG-Y file
static (int): Number of top samples to remove (static correction)
trace_remove (int): Number of traces to remove from the end
x (float): X-coordinate offset
y (float): Y-coordinate offset
sub_aver (int): Window size for background subtraction
amplify (float): Amplitude scale factor for visualization
"""
with segyio.open(filename, 'r', ignore_geometry=True) as segyfile:
raw_traces = segyfile.trace.raw[:]
data_meta = raw_traces.T[static:, :-trace_remove]
data=data_meta[0:500]
# Subtract local mean background (clutter removal)
reference_mean = sliding_window_mean(data, sub_aver)
data = data - reference_mean
# Apply tapering to suppress edge effects
data = np.apply_along_axis(taper_field, 0, data)
# Perform FK migration (custom)
data, twtt, mig_pos = mig_fk.fkmig(data, dt=0.02425e-9, dx=0.005, v=1.10e8)
# Compute envelope using Hilbert transform
data_envelop = np.abs(hilbert(data, axis=0))
# Plot migrated image
plt.title(filename[7:-4])
plt.imshow(data * amplify, cmap='gray_r', aspect='auto',
vmin=-np.max(np.abs(data)), vmax=np.max(np.abs(data)))
plt.colorbar()
plt.show()
# Plot envelope
plt.title(filename[7:-4] + " Envelope")
plt.imshow(data_envelop * amplify, cmap='gray_r', aspect='auto',
vmin=0, vmax=0.0040)
plt.colorbar()
plt.show()
# Save envelope to .dat file
write_to_dat_append(data_envelop, x, y, file)
# Main process for batch loading and writing
with open('3D.dat', 'w') as file:
load('./data/x0y0.sgy', static=35, trace_remove=1, x=0, y=0, sub_aver=51, amplify=3)
load('./data/x0y5.sgy', static=35, trace_remove=1, x=0, y=5, sub_aver=51, amplify=5)
load('./data/x0y10.sgy', static=35, trace_remove=1, x=0, y=10, sub_aver=51, amplify=10)
load('./data/x0y15.sgy', static=35, trace_remove=1, x=0, y=15, sub_aver=51, amplify=10)
load('./data/x0y20.sgy', static=35, trace_remove=1, x=0, y=20, sub_aver=51, amplify=10)
load('./data/x10y25.sgy', static=35, trace_remove=1, x=10, y=25, sub_aver=51, amplify=10)
load('./data/x20y30.sgy', static=35, trace_remove=1, x=20, y=30, sub_aver=51, amplify=10)
load('./data/x30y35.sgy', static=35, trace_remove=15, x=30, y=35, sub_aver=51, amplify=10)
load('./data/x30y40.sgy', static=35, trace_remove=1, x=30, y=40, sub_aver=51, amplify=10)
load('./data/x40y45.sgy', static=35, trace_remove=1, x=40, y=45, sub_aver=51, amplify=10)
load('./data/x45y50.sgy', static=35, trace_remove=14, x=45, y=50, sub_aver=51, amplify=10)

430
mig_fk.py 普通文件
查看文件

@@ -0,0 +1,430 @@
#! /usr/bin/env python
#
# Implements an FK (Stolt) migration routine.
#
# Dmig, tmig, xmig = fkmig(D, dt, dx, v, params)
#
# D data array
# dt temporal sample rate
# dx spatial sample rate
# v constant migration velocity
# params migration parameters (not yet implemented)
#
# Code translated from CREWES MATLAB algorithm, and similar restrictions
# presumably apply. The original license terms are included below.
#
# BEGIN TERMS OF USE LICENSE
#
# This SOFTWARE is maintained by the CREWES Project at the Department
# of Geology and Geophysics of the University of Calgary, Calgary,
# Alberta, Canada. The copyright and ownership is jointly held by
# its author (identified above) and the CREWES Project. The CREWES
# project may be contacted via email at: crewesinfo@crewes.org
#
# The term 'SOFTWARE' refers to the Matlab source code, translations to
# any other computer language, or object code
#
# Terms of use of this SOFTWARE
#
# 1) Use of this SOFTWARE by any for-profit commercial organization is
# expressly forbidden unless said organization is a CREWES Project
# Sponsor.
#
# 2) A CREWES Project sponsor may use this SOFTWARE under the terms of the
# CREWES Project Sponsorship agreement.
#
# 3) A student or employee of a non-profit educational institution may
# use this SOFTWARE subject to the following terms and conditions:
# - this SOFTWARE is for teaching or research purposes only.
# - this SOFTWARE may be distributed to other students or researchers
# provided that these license terms are included.
# - reselling the SOFTWARE, or including it or any portion of it, in any
# software that will be resold is expressly forbidden.
# - transfering the SOFTWARE in any form to a commercial firm or any
# other for-profit organization is expressly forbidden.
#
#
#from __future__ import print_function
import math
import numpy as np
import pdb, traceback
from tqdm import tqdm
def csinci():
""" Complex valued sinc function interpolation.
trout = csinci(trin, t, tout, sizetable)
"""
def fktran(D, t, x, ntpad=None, nxpad=None, percent=0., ishift=1):
""" F-K transform using fft on time domain and ifft on space domain. """
nsamp = D.shape[0]#时间采集点数
ntr = D.shape[1]#x距离采集点数
if len(t) != nsamp:
raise Exception('Time domain length is inconsistent in input')
if len(x) != ntr:
raise Exception('Space domain length is inconsistent in input')
if ntpad is None:
ntpad = 2**nextpow2(t) #""" Gives the next power of 2 larger than a. """return np.ceil(np.log(a) / np.log(2)).astype(int)
if nxpad is None:
nxpad = 2**nextpow2(x)
# Get real values of transform with fftrl
specfx, f = fftrl(D, t, percent, ntpad)###傅里叶变换
# Taper and pad in space domain
if percent > 0.:
mw = np.tile(mwindow(ntr, percent), (ntr, 1))
specfx = specfx * mw
if ntr < nxpad:
ntr = nxpad # this causes ifft to apply the x padding
spec = np.fft.ifft(specfx.T, n=ntr, axis=0).T
# Compute kx
kxnyq = 1. / (2. * (x[1] - x[0]))
dkx = 2. * kxnyq / ntr
kx = np.hstack([np.arange(0, kxnyq, dkx), np.arange(-kxnyq, 0, dkx)])
if ishift:
tmp = zip(kx, spec)
tmp.sort()
kx = [i[0] for i in tmp]
spec = [i[1] for i in tmp]
return spec, f, kx
def fftrl(s, t, percent=0.0, n=None):
""" Returns the real part of the forward Fourier transform. """
# Determine the number of traces in ensemble
l = s.shape[0]
m = s.shape[1]
ntraces = 1
itr = 0 # transpose flag
if l == 1:
nsamps = m
itr = 1
s = s.T
elif m == 1:
nsamps = l
else:
nsamps = l
ntraces = m
if nsamps != len(t):
t = t[0] + (t[1] - t[0]) * np.arange(0, nsamps)
if n is None:
n = len(t)
# Apply the taper
if percent > 0.0:
mw = np.tile(mwindow(nsamps, percent), (ntraces, 1))
s = s * mw
# Pad s if needed
if nsamps < n:
s = np.vstack([s, np.zeros([n-nsamps, ntraces])])
nsamps = n
# Do the transformation
spec = np.fft.fft(s, n=nsamps, axis=0)
spec = spec[:int(n/2)+1, :] # save only positive frequencies
# Build the frequency vector
fnyq = 1. / (2 * (t[1] - t[0]))
nf = spec.shape[0]
df = 2.0 * fnyq / n
f = df * np.arange(0,nf).T
if itr:
f = f.T
spec = spec.T
return spec, f
def ifktran(spec, f, kx, nfpad=None, nkpad=None, percent=0.0):
""" Inverse f-k transform.
Arguments:
spec complex valued f-k series
f frequency components for rows of spec
kx wavenumber components for columns of spec
nfpad size to pad spec rows to
nkpad size to pad spec columns to
percent controls cosine taper
Returns:
D 2-d array; one trace per column
t time coordinates for D
x space coordinates for D
"""
nf,nkx = spec.shape
if len(f) != nf:
raise Exception('frequency coordinate vector is wrong size')
elif len(kx) != nkx:
raise Exception('wavenumber coordinate vector is wrong size')
if nfpad is None:
nfpad = 2**nextpow2(len(f))
if nkpad is None:
nkpad = 2**nextpow2(len(kx))
# Determine if kx needs to be wrapped
if kx[0] < 0.0:
# Looks unwrapped (is this wise?)
ind = kx >= 0.0
kx = np.hstack([kx[ind], kx[np.arange(ind[0])]])
spec = np.hstack([spec[:,ind], spec[:,np.arange(ind[0])]])
else:
ind = False
# Taper and pad in kx
if percent > 0.0:
mw = mwindow(nkx, percent)
if ind.any():
mw = np.hstack([mw[ind], mw[np.arange(ind[0])]])
mw = mw.repeat(nkz, axis=0)
spec = spec * mw
if nkx < nkpad:
nkx = nkpad
# Performs the transforms
specfx = np.fft.fft(spec, nkx)
D, t = ifftrl(specfx, f)
# Compute x
dkx = kx[1] - kx[0]
xmax = 1.0 / dkx
dx = xmax / nkx
x = np.arange(0, xmax, dx)
return D, t, x
def ifftrl(spec, f):
""" Inverse Fourier transform for real-valued series.
Arguments:
spec input spectrum
f input frequency coordinates
Returns:
r output trace
t output time vector
"""
m,n = spec.shape # Will be a problem if spec is 1-dimensional
itr = 0
if (m == 1) or (n == 1):
if m == 1:
spec = spec.T
itr = 1
nsamp = len(spec)
ntr = 1
else:
nsamp = m
ntr = n
# Form the conjugate symmetric complex spectrum expected by ifft
# Test for nyquist
nyq = 0
if (spec[-1] == np.real(spec[-1])).all():
nyq = 1
if nyq:
L1 = np.arange(nsamp)
L2 = L1[-2:0:-1]
else:
L1 = np.arange(nsamp)
L2 = L1[-2:0:-1] # WTF? -njw
symspec = np.vstack([spec[L1,:], np.conj(spec[L2,:])])
# Transform the array
r = (np.fft.ifft(symspec.T)).real.T
# Build the time vector
n = len(r)
df = f[1] - f[0]
dt = 1.0 / (n*df)
t = dt * np.arange(n).T
if itr == 1:
r = r.T
t = t.T
return r, t
def mwindow(n, percent=10.):
""" Creates a boxcar window with raised-cosine tapers. """
if type(n) is not int and type(n) is not float:
n = len(n)
# Compute the hanning function
if percent > 50. or percent < 0.:
raise Exception('Invalid percent in function mwindow (={0})'.format(percent))
m = 2.0 * math.floor(percent * n / 100.)
h = np.hanning(m)
return np.hstack([h[:m/2], np.ones([n-m]), h[m/2:]])
def mwhalf(n, percent=10.):
""" Half mwindow. """
if type(n) is not int and type(n) is not float:
n = len(n)
# Compute the hanning function
if percent > 100. or percent < 0.:
raise Exception('Invalid percent in function mwhalf (={0})'.format(percent))
m = int(math.floor(percent * n / 100.))
h = np.hanning(2*m)
return np.hstack([np.ones([n-m]), h[m:0:-1]])
def nextpow2(a):
""" Gives the next power of 2 larger than a. """
return np.ceil(np.log(a) / np.log(2)).astype(int)
def fkmig(D, dt, dx, v, params=None):
nsamp = D.shape[0]##时间采集点数
ntr = D.shape[1]#x采集点数
t = np.arange(0, nsamp) * dt#生成时间linspace
x = np.arange(0, ntr) * dx#生成距离linspace
interpolated = True
fnyq = 1.0 / (2.0*dt)#采样频率,存疑,为什么是2
knyq = 1.0 / (2.0*dx)#
tmax = t[-1]#最大时间
xmax = abs(x[-1]-x[0])#最大距离
# Deal with parameters#
if params == None:
fmax = 0.6 * fnyq
fwid = 0.2 * (fnyq - fmax)#中频?
dipmax = 85.0
dipwid = 90.0 - dipmax#5
tpad = min([0.5 * tmax, abs(tmax / math.cos(math.pi*dipmax / 180.0))])
xpad = min([0.5 * xmax, xmax / math.sin(math.pi*dipmax / 180.0)])
padflag = 1
intflag = 3
cosflag = 1
lsinc = 1
ntable = 25
mcflag = 0 # Faster, less memory-efficient transform (not implemented)
kpflag = 50.0
# Apply padding#填充0值,优化傅里叶变换的性能
# tpad
nsampnew = int(2.0**nextpow2( round((tmax+tpad) / dt + 1.0) ))# """ Gives the next power of 2 larger than a. return np.ceil(np.log(a) / np.log(2)).astype(int)
tmaxnew = (nsampnew-1)*dt
tnew = np.arange(t[0], tmaxnew+dt, dt)#新的采样linespace
ntpad = nsampnew-nsamp
D = np.vstack([D,np.zeros([ntpad,ntr])])
# xpad
ntrnew = 2**nextpow2( round((xmax+xpad) / dx + 1) )#
xmaxnew = (ntrnew-1)*dx + x[0]
xnew = np.arange(x[0], xmaxnew+dx, dx)#新的采样linespace
nxpad = ntrnew - ntr
D = np.hstack([D, np.zeros([nsampnew,nxpad])])
# Forward f-k transform
fkspec, f, kx = fktran(D, tnew, xnew, nsampnew, ntrnew, 0, 0)
df = f[1] - f[0]
nf = len(f)
# Compute frequency mask
ifmaxmig = int(round((fmax+fwid) / df + 1.0))
pct = 100.0 * (fwid / (fmax+fwid))
fmask = np.hstack([mwhalf(ifmaxmig,pct), np.zeros([nf-ifmaxmig])])
fmaxmig = (ifmaxmig-1)*df # i.e. fmax+fwid to nearest sample
# Now loop over wavenumbers
ve = v / 2.0 # exploding reflector velocity
dkz = df / ve
kz = (np.arange(0,len(f)) * dkz).T
kz2 = kz ** 2
th1 = dipmax * math.pi / 180.0
th2 = (dipmax+dipwid) * math.pi / 180.0
if th1 == th2:
print("No dip filtering")
for j,kxi in enumerate(kx):
# Evanescent cut-off
fmin = abs(kxi) * ve
ifmin = int(math.ceil(fmin / df)) + 1
# Compute dip mask
if th1 != th2:
# First physical frequency excluding dc
ifbeg = max([ifmin, 1])+1
# Frequencies to migrate
ifuse = np.arange(ifbeg, ifmaxmig+1)
# if len(ifuse) == 1: # This was before 4/18/2020
if len(ifuse) <= 1: # Alain changed this on 4/18/2020
# Special case
dipmask = np.zeros(f.shape)
dipmask[ifuse-1] = 1
else:
# Physical dips for each frequency
theta = np.arcsin(fmin / f[ifuse])
# Sample number to begin ramp
if1 = int(round(fmin / (math.sin(th1) * df)))
if1 = max([if1, ifbeg])
# sample number to end ramp
if2 = int(round(fmin / (math.sin(th2) * df)))
if2 = max([if2, ifbeg])
# Initialize mask to zeros
dipmask = np.zeros(f.shape)
# Pass these dips
dipmask[if1:nf-1] = 1
dipmask[if2:if1] = 0.5 + 0.5 * np.cos(
(theta[np.arange(if2, if1) - ifbeg] - th1)
* math.pi / float(th2-th1))
else:
dipmask = np.ones(f.shape)
# Apply masks
tmp = fkspec[:, j] * fmask * dipmask
# Compute f that map to kz
fmap = ve * np.sqrt(kx[j]**2 + kz2)
# Contains one value for each kz giving the frequency
# that maps there to migrate the data
# Many of these frequencies will be far too high
ind = np.vstack(np.nonzero(fmap <= fmaxmig)).T
# ind is an array of indicies of fmap which will always start at 1
# and end at the highest f to be migrated
# Now map samples by interpolation
fkspec[:, j] *= 0.0 # initialize output spectrum to zero
if len(ind) != 0:
# Compute cosine scale factor
if cosflag:
if fmap[ind].all() == 0:
scl = np.ones(ind.shape[0])
li = ind.shape[0]
scl[1:li] = (ve * kz[ind[1:li]] / fmap[ind[1:li]])[:,0]
else:
scl = ve * kz[ind] / fmap[ind]
else:
scl = np.ones(ind.shape[0])
if intflag == 0:
# Nearest neighbour interpolation
ifmap = (fmap[ind] / df).astype(int)
fkspec[ind, j] = (scl.squeeze() \
* tmp[ifmap.squeeze()]).reshape([-1,1])
elif intflag == 1:
# Complex sinc interpolation
fkspec[ind, j] = scl \
* csinci(tmp, f, fmap[ind], np.hstack([lsinc,ntable]))
elif intflag == 2:
# Spline interpolation
# Not implemented
pass
elif intflag == 3:
# Linear interpolation
r_interp = scl.squeeze() \
* np.interp(fmap[ind], f, tmp.real).squeeze()
j_interp = scl.squeeze() \
* np.interp(fmap[ind], f, tmp.imag).squeeze()
fkspec[ind, j] = (r_interp + j_interp * 1j).reshape([-1,1])
# Inverse transform
Dmig, tmig, xmig = ifktran(fkspec, f, kx)
# Remove padding, if desired
if padflag:
Dmig = Dmig[:nsamp, :ntr]
tmig = tmig[:nsamp]
xmig = xmig[:ntr]
return Dmig, tmig, xmig