Exercise 7 : Seismic imaging

In this exercise, we conduct a small 2D seismic imaging experiment on a 4-layer model example with JUDI. We suggest you use a Docker image to run the experiment so that software packages are properly installed. To use the docker image, first install docker. Then, in the terminal, do

docker run -p 8888:8888 ddjj1118/judi_eas_project:v4.0

Running this command will produce an output that looks like


    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
           http://af637030c092:8888/?token=8f6c664eb945f9c6b7cd72669fef04a6dc70c08194cb87e9
        or http://127.0.0.1:8888/?token=8f6c664eb945f9c6b7cd72669fef04a6dc70c08194cb87e9

Copy paste the URL in your browser and replace (af637030c092 or 127.0.0.1) by localhost. You will then be directed to a jupyter folder that contains the notebooks for the projects.

First, make sure we have the latest version of Devito installed. You can do this by click NEW, then go for TERMINAL in the browser with jupyter folder, then do

pip install devito==4.3

Then, we can create a new julia script and run the experiment in it.

Set up the experiment

## First do using Pkg; Pkg.add(xxx); Pkg.update(xxx) to make sure they are installed and in the latest version
using JUDI, PyPlot, Images, JOLI, IterativeSolvers, LinearAlgebra, Printf, Statistics

Here, we set up a 4-layer model, where the velocities in each layer are 1.5/2.5/3/3.5 km/s. We often refer the first layer as the water layer/column in marine acquisition.

# number of gridpoints
n = (201, 101)

# Grid spacing
d = (10.0, 10.0)

# Origin
o = (0., 0.)

# Velocity [km/s]
v = ones(Float32,n) .+ 0.5f0
v[:,20:50] .= 2.5f0
v[:,51:71] .= 3f0
v[:,71:end] .= 3.5f0

Wave-equation simulations in JUDI are based on squared slowness m. Also, we make up a background model m0 which is a smoothed version of the ground truth one. To make things simple, we assume to know the exact depth of ocean bottom and keep the water column fixed in the background model. The dm, as the difference of m and m0, only contains the sharp reflectors.

# Slowness squared [s^2/km^2]
m = (1f0 ./ v).^2
m0 = convert(Array{Float32,2},imfilter(m, Kernel.gaussian(2)))
m0[:,1:19] = m[:,1:19]
dm = vec(m - m0)

figure();
subplot(1,3,1)
imshow(reshape(m,n)');title("m")
subplot(1,3,2)
imshow(reshape(m0,n)');title("m0")
subplot(1,3,3)
imshow(reshape(dm,n)',cmap="Greys");title("dm")

m and m0 are stored in model structure in JUDI, with physical information.

model0 = Model(n, d, o, m0; nb = 80)
model = Model(n, d, o, m; nb = 80)

We set up the source geometry and receiver geometry as below. Simulations are in 2D so y-direction is always 0. The sources are assumed to fire one-by-one while receivers are fixed.


nsrc = 8 # num of sources
xsrc = convertToCell(range(900f0, stop=1100f0, length=nsrc)) # in [m]
ysrc = convertToCell(range(0f0, stop=0f0, length=nsrc)) # in [m]
zsrc = convertToCell(range(6f0, stop=6f0, length=nsrc)) # in [m]

timeS = 1000f0 # recording time [ms]
dtS = 1f0 # time sampling [ms]

srcGeometry = Geometry(xsrc,ysrc,zsrc; dt=dtS, t=timeS)

nrec = 100 # num of receivers
xrec = range(d[1], stop=(n[1]-1)*d[1], length=nrec) # in [m]
yrec = 0f0  # in [m]
zrec = range(10f0, stop=10f0, length=nrec)  # in [m]

timeR = 1000f0 # recording time [ms]
dtR = 1f0 # time sampling [ms]

recGeometry = Geometry(xrec,yrec,zrec;dt=dtR,t=timeR, nsrc=nsrc)

Set up the source with Ricker wavelet


f0 = 0.02f0  # 20 Hz wavelet
wavelet = ricker_wavelet(timeS, dtS, f0)
q = judiVector(srcGeometry, wavelet)

Set up computational time step


# Set up info structure for linear operators
ntComp = get_computational_nt(srcGeometry, recGeometry, model)
info = Info(prod(n), nsrc, ntComp)

Set up forward modeling operators in ground truth velocity, background velocity, and migration operator

F = judiModeling(info, model, srcGeometry, recGeometry) # forward modeling w/ true model
F0 = judiModeling(info, model0, srcGeometry, recGeometry) # forward modeling w/ background model
J = judiJacobian(F0,q) # demigration operator (adjoint of J is migration)

Generate data

Generate data in ground truth velocity and background velocity

dobs = F*q
dobs0 = F0*q

Task: plot a single shot record of dobs and dobs0. Choose the same source. What do you see in the shot record? If there are a couple of events, try to match them up with the corresponding reflectors.

# hint: seismic data is as a judiVector
# you can do this by figure();imshow(dobs.data[1],vmin=-0.02*norm(dobs.data[1],Inf),cmap="seismic",vmax=0.02*norm(dobs.data[1],Inf),aspect="auto")
# This plots the shot record in correct velocity generated by the 1st source
# IMPORTANT: always adjust vmin, vmax, aspect so that we can see the events clearly

You can also generate a linearized shot record by

dlin = J*dm

Task: do the same comparison of dlin and dobs-dobs0. What do you see? Why do you observe this theoretically (think about math)?

Reverse time migration (RTM)

From now on, we will only focusing on migrating the linearized data for simplicity. First, we can try reverse time migration by

rtm1 = J'*dlin

To make the RTM image cleaner and with higher quality, Witte et al developed an inverse-scattering imaging condition (ISIC), which has been incorporated in JUDI.

J.options.isic = true
rtm2 = J'*dlin

Task: compare RTM results w/ and w/o ISIC, what do you see? Remember again to clip the vmin/vmax to see all the events in the images.

# hint: rtm1/rtm2 are PhysicalParameters. Do rtm1.data, rtm2.data to access the value. Again take care of vmin, vmax, aspect of plotting.

Least-squares reverse time migration (LS-RTM)

Seismic imaging researchers are not always satisfied with RTM. Seismic imaging basically solves the optimization problem

\min_{\mathbf{\delta m}} \frac{1}{2}\sum_{i=1}^{n_s}\|\mathcal{J}_i\mathbf{\delta m} - \mathbf{\delta d}\|_2^2

where RTM is only taking a full gradient of the objective w.r.t. \delta m. To get better images, we can minimize the objective by LSQR and apply a right preconditioner


# Right Preconditioner
Tm = judiTopmute(model0.n, 19, 2)  # Mute water column
S = judiDepthScaling(model0)
Mr = S*Tm

## vanilla LSQR

x1 = 0f0 .* model0.m

lsqr!(x1,J*Mr,dlin;maxiter=2,atol=0f0,verbose=true)

The final solution is given by Mr*x1 (Mr is a right preconditioner so we are actually minimizing \frac{1}{2}\|\mathcal{J}\mathbf{M_r}\mathbf{x}- \mathbf{\delta d}\|_2^2)

Task: compare LS-RTM result with the previous RTM result, what do you see?

Sparsity-promoting least-squares reverse time migration (SPLS-RTM)

The start-of-the-art imaging technique is to do LS-RTM while promoting sparsity of solution in Curvelet domain, see Witte et al. This can be achieved by linearized Bregman iterations, as shown below


# Soft thresholding functions and Curvelet transform
soft_thresholding(x::Array{Float64}, lambda) = sign.(x) .* max.(abs.(x) .- convert(Float64, lambda), 0.0)
soft_thresholding(x::Array{Float32}, lambda) = sign.(x) .* max.(abs.(x) .- convert(Float32, lambda), 0f0)

n = model0.n
C0 = joCurvelet2D(n[1], 2*n[2]; zero_finest = false, DDT = Float32, RDT = Float64)

function C_fwd(im, C, n)
    im = hcat(reshape(im, n), reshape(im, n)[:, end:-1:1])
    coeffs = C*vec(im)
    return coeffs
end

function C_adj(coeffs, C, n)
    im = reshape(C'*coeffs, n[1], 2*n[2])
    return vec(im[:, 1:n[2]] .+ im[:, end:-1:n[2]+1])
end

C = joLinearFunctionFwd_T(size(C0, 1), n[1]*n[2],
                          x -> C_fwd(x, C0, n),
                          b -> C_adj(b, C0, n),
                          Float32,Float64, name="Cmirrorext")


src_list = Set(collect(1:nsrc))
batchsize = 2
lambda = 0f0

x2 = 0f0 .* model0.m
z = deepcopy(x2)

niter = 8

# Main loop
for j = 1:niter
    # Select batch and set up left-hand preconditioner
    length(src_list) < batchsize && (global src_list = Set(collect(1:nsrc)))
    i = [pop!(src_list) for b=1:batchsize]
    println("LS-RTM Iteration: $(j), imaging sources $(i)")
    flush(Base.stdout)

    residual = J[i]*Mr*x2-dlin[i]
    phi = 0.5 * norm(residual)^2
    g = Mr'*J[i]'*residual

    # Step size and update variable
    t = Float32.(2*phi/norm(g)^2)

    # Update variables and save snapshot
    global z -= t*g
    C_z = C*z
    (j==1) && (global lambda = quantile(abs.(C_z), .95))   # estimate thresholding parameter in 1st iteration
    global x2 = adjoint(C)*soft_thresholding(C_z, lambda)

    @printf("At iteration %d function value is %2.2e and step length is %2.2e \n", j, phi, t)
    @printf("Lambda is %2.2e \n", lambda)
end

The solution is given by Mr*x2.

Task: compare the image from LSQR and linearized Bregman. What do you see? Remember again about clipping

Task: the images recovered above are focusing only in the central region. Why? (Hint: check source/receiver locations) How can we get a full illumination of the image? Make experiments to verify.

Task: change acquisition to be transmission -- e.g. put sources as a vertical line on the left, receivers on the right. Show what RTM image looks like. Is it the same as you got from the reflective seismic?