Exercise 6: From processing to inversion II

Contents:

  • Kronecker
  • NMO-Stack-deconvolution
  • Inverting the Radon transform

Kronecker

Given a matrix X, we often want to apply operations along both dimensions. For example, if each column is a trace we can do a temporal fourier transform of each trace as follows

using JOLI, GenSPGL, PyPlot, FFTW, LinearAlgebra
# dummy matrix
n1 = 10
n2 = 5
X = joComplex.(randn(n1,n2))
# fft along first dimension
F1 = joDFT(n1; DDT=joComplex)
Y  = F1*X
10×5 Array{Complex{Float64},2}:
   1.41557+0.0im        -1.60638+0.0im       …   0.430018+0.0im      
  0.672767+0.450352im  -0.938118+0.759956im       -0.1883-0.991497im 
  0.406392+0.205739im  -0.302384+0.247512im      0.631065-1.30804im  
 -0.618123-0.848047im  -0.121208+0.443261im     -0.646604-1.17498im  
 -0.787335-0.450483im  0.0533324-0.395285im     -0.760621+0.0204424im
  0.294818+0.0im       -0.429069+0.0im       …  -0.250248+0.0im      
 -0.787335+0.450483im  0.0533324+0.395285im     -0.760621-0.0204424im
 -0.618123+0.848047im  -0.121208-0.443261im     -0.646604+1.17498im  
  0.406392-0.205739im  -0.302384-0.247512im      0.631065+1.30804im  
  0.672767-0.450352im  -0.938118-0.759956im       -0.1883+0.991497im

We can do an fft along the second dimension as follows

F2 = joDFT(n2; DDT=joComplex)
Y  = transpose(F2*copy(transpose(X)));

Finally, we can combine both in one step as follows

Y = transpose(F2*copy(transpose(F1*X)));

We can do the equivalent operation on the vectorized version of X via the Kronecker product. The formula is :

$ \mathrm{vec}(AXB) = (B^T\otimes A)\mathrm{vec}(X) $.

where $\mathrm{vec}$ vectorizes a matrix $X(:)$.

Use joKron to construct a 2D fft operator that works on a vectorized version of X, X(:). Show that the result is the same as when using the operators F1 and F2 separately.

# 2D FFT operator
F12 = joKron(F2,F1);

# compare: F12*X(:) should be the same as Y(:)
norm(F12*X[:] - Y[:])

0.0

NMO-Stack-deconvolution

We revisit the NMO and stack operations we saw a few weeks before, but we will use it backwards. Remember the conventional flow was Data -> NMO corrected data -> stack -> image We will now traverse this chain in the reverse order, each time using the adjoint of the operations.

The reflectivity (image) can be represented by a convolution of a spike train with a wavelet, as we saw last week. We will build this chain of operations reflectivity -> convolved reflectivity -> NMO corrected data -> data, step-by-step.

First, define a time and offset axis.

# time and offset grid
t = Float64.(0:.004:1);  nt = length(t);
h =  Float64.(0.0:10.0:1000.0); nh = length(h);

We make a reflectivity series with 3 spikes and define a wavelet.

# reflectivity
r = zeros(nt,1);
r[51] = 1
r[101] = -.5
r[151] = .75

# wavelet
w = (1 .-2*1e3*(t .-.1).^2).*exp.(-1e3 .*(t .-.1).^2)
251-element Array{Float64,1}:
 -0.0008625986654872107
 -0.0017333620023927386
 -0.003359640068423334 
 -0.00627815407118934  
 -0.011305429764281264 
 -0.019606375823452423 
 -0.032722754572662376 
 -0.05251269265727092  
 -0.08094144772594736  
 -0.11966839901351634  
 -0.16940707917321376  
 -0.22910148611303477  
 -0.29505929933463465  
  ⋮                    
 -8.75952886e-316      
 -9.2021e-319          
 -0.0                  
 -0.0                  
 -0.0                  
 -0.0                  
 -0.0                  
 -0.0                  
 -0.0                  
 -0.0                  
 -0.0                  
 -0.0

The convolution is done by using the FFT SPOT operators, just like in the last exercise.

# convolution operator
C = joDFT(nt)'*joDiag(fft(w))*joDFT(nt);

Next, we need to extend the the reflectivity to be a function of time and offset. We are trying to undo the stack operation to create NMO corrected data. Let's first look at the stack. Given a matrix, we can stack the columns by multiplying with a vector of all ones:

# test matrix
X =[1 2; 3 4; 5 6]
Y = X*[1;1]
3-element Array{Int64,1}:
  3
  7
 11
  • Construct a JOLI operator that stacks a vectorized input matrix of size nt x nh along the columns. Use joDirac to define an identity operator.
  • Apply the operators C and S to the vector r to get something that resembles NMO-corrected data. You can reshape the vector into a matrix by using reshape. Plot the result.

The next step is to construct an NMO operator. Please follow the function nmo in Exercise2 and define a JOLI operator for a constant velocity of 2000 m/s. Don't forget to take care of the shape of input and output (they should both be vectors in proper lengths). You can do this by simply filling the blanks below. Apply it to the result of the previous exercise and plot the result.

function nmo(cmp, t, off, v,flag)
# NMO correction and adjoint 
#
# use:
#   out = nmo(in,t,h,v,flag)
#
# input:
#   in   - data matrix of size [length(t) x length(h)], each column is a trace 
#   t    - time vector [s]
#   offsets    - offset vector [m]
#   v    - NMO velocity [m/s] as vector of size [length(t) x 1].
#   flag - 1:forward, -1:adjoint
#
# output
#   out  - data matrix of size [length(t) x length(h)], each column is a trace 
    if size(cmp, 2) == 1
        return cmp
    end
    # size of data
    nt, nh = size(cmp)
    # make sure t and v are column vectors
    t = t[:]
    v = v[:]
    # initialize output
    out = zeros(nt, nh)
    # loop over offset

    for i = 1:nh
        # NMO traveltime
        tau = sqrt.(t.^2 + off[i].^2 ./v.^2);

        A = getLA(t,tau);
        # interpolate, forward or adjoint
        new = zeros(size(out[:,i]))
        if flag == 1
            new[1:size(A*cmp[:,i])[1]] = A*cmp[:,i]
        else
            new[1:size(A'*cmp[:,i])[1]] = A'*cmp[:,i]
        end
        out[:,i] = new[1:nt]
        #cmp
    end
    return vec(out)
end

function nmo_forward(vec_cmp,t,h,vel) 
    mat_cmp = _ # to be done
    return nmo(mat_cmp,t,h,vel,1)
end
function nmo_adjoint(vec_cmp,t,h,vel) 
    mat_cmp = _ # to be done
    return nmo(mat_cmp,t,h,vel,-1)
end

function joNMO(t,h,vel, n)
    C = joLinearFunctionFwd_T(n, n,
        v -> nmo_forward(v, t,h,vel),
        w -> nmo_adjoint(w, t,h,vel),
        Float64, Float64,name="NMO operator")
end
B = joNMO(_, _, _, _)

Now, define a combined operator that predicts data given a spike train.

  • Check that your combined operator satisfies the dottest
  • Make data for the spike train r and add some noise.
  • Invert the operator with both lsqr and spgl1 (see previous exercise).

Inverting the Radon transform

In the previous exercise we saw that the Radon transform is not unitary. This means that its adjoint is not its inverse. Here, we will set up a JOLI operator for the Radon transform and invert it using lsqr and spgl1. If the computation takes too long you can use a coarser sampling of the q axis.

  • read the data parab.segy
  • Set up a parabolic radon transform JOLI operator , R=joRadon(....; DDT=joFloat)
  • Plot the data in the Radon domain, and go back to the orininal data using the adjoint. Compare to the original data. - - What do you notice?
  • You want to obtain data in the Radon domain b for which the predicted data in the t,h domain is close to the original data. How would you do this?.
  • Setup a damped least-squares system and invert with lsqr. Try different damping parameters and explain what you see. Hint: us lsqr(..., damp=) for a damped least square.
  • Use the original system and invert with spgl1(A,b,0,tolerance).
  • Describe a possible application of this technique in seismic processing

Do not forget to turn your data into Float64