-
Notifications
You must be signed in to change notification settings - Fork 32
/
GeometryStructure.jl
421 lines (342 loc) · 16.7 KB
/
GeometryStructure.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# Source/receiver geometry structure
# Author: Philipp Witte, [email protected]
# Date: January 2017
#
export Geometry, compareGeometry, GeometryIC, GeometryOOC, get_nsrc, n_samples
abstract type Geometry{T} end
const CoordT = Union{Vector{T}, Vector{Vector{T}}} where T<:Number
(::Type{CoordT})(x::Vector{Any}) = rebuild_maybe_jld(x)
# In-core geometry structure for seismic header information
mutable struct GeometryIC{T} <: Geometry{T}
xloc::CoordT # Array of receiver positions (fixed for all experiments)
yloc::CoordT
zloc::CoordT
dt::Array{T,1}
nt::Array{Integer,1}
t::Array{T,1}
end
getproperty(G::GeometryIC, s::Symbol) = s == :nrec ? length.(G.xloc) : getfield(G, s)
# Out-of-core geometry structure, contains look-up table instead of coordinates
mutable struct GeometryOOC{T} <: Geometry{T}
container::Array{SegyIO.SeisCon,1}
dt::Array{T,1}
nt::Array{Integer,1}
t::Array{T,1}
nrec::Array{Integer,1}
key::String
segy_depth_key::String
end
display(G::Geometry) = println("$(typeof(G)) wiht $(length(G.nt)) sources")
show(io::IO, G::Geometry) = print(io, "$(typeof(G)) wiht $(length(G.nt)) sources")
show(io::IO, ::MIME{Symbol("text/plain")}, G::Geometry) = println(io, "$(typeof(G)) wiht $(length(G.nt)) sources")
######################## shapes easy access ################################
get_nsrc(g::GeometryIC) = length(g.xloc)
get_nsrc(g::GeometryOOC) = length(g.container)
get_nsrc(S::SeisCon) = length(S)
get_nsrc(S::Vector{SeisCon}) = length(S)
get_nsrc(S::SeisBlock) = length(unique(get_header(S, "FieldRecord")))
n_samples(g::GeometryOOC, nsrc::Integer) = sum([g.nrec[j]*g.nt[j] for j=1:nsrc])
n_samples(g::GeometryIC, nsrc::Integer) = sum([length(g.xloc[j])*g.nt[j] for j=1:nsrc])
n_samples(g::Geometry) = n_samples(g, get_nsrc(g))
################################################ Constructors ####################################################################
"""
GeometryIC
xloc::Array{Array{T, 1},1}
yloc::Array{Array{T, 1},1}
zloc::Array{Array{T, 1},1}
dt::Array{T,1}
nt::Array{Integer,1}
t::Array{T,1}
Geometry structure for seismic sources or receivers. Each field is a cell array, where individual cell entries
contain values or arrays with coordinates and sampling information for the corresponding shot position. The
first three entries are in meters and the last three entries in milliseconds.
GeometryOOC{T} <: Geometry{T}
container::Array{SegyIO.SeisCon,1}
dt::Array{T,1}
nt::Array{Integer,1}
t::Array{T,1}
nrec::Array{Integer,1}
key::String
segy_depth_key::String
Constructors
============
Only pass `dt` and `n` and automatically set `t`:
Geometry(xloc, yloc, zloc; dt=[], nt=[])
Pass single array as coordinates/parameters for all `nsrc` experiments:
Geometry(xloc, yloc, zloc, dt=[], nt=[], nsrc=1)
Create geometry structure for either source or receivers from a SegyIO.SeisBlock object.
`segy_depth_key` is the SegyIO keyword that contains the depth coordinate and `key` is
set to either `source` for source geometry or `receiver` for receiver geometry:
Geometry(SeisBlock; key="source", segy_depth_key="")
Create geometry structure for from a SegyIO.SeisCon object (seismic data container):
Geometry(SeisCon; key="source", segy_depth_key="")
Examples
========
(1) Set up receiver geometry for 2D experiment with 4 source locations and 80 fixed receivers:
xrec = range(100,stop=900,length=80)
yrec = range(0,stop=0,length=80)
zrec = range(50,stop=50,length=80)
dt = 4f0
t = 1000f0
rec_geometry = Geometry(xrec, yrec, zrec; dt=dt, t=t, nsrc=4)
(2) Set up corresponding source geometry (coordinates can be of type `linspace` or regular arrays):
xsrc = [200,400,600,800]
ysrc = [0,0,0,0]
zsrc = [50,50,50,50]
src_geometry = Geometry(xsrc, ysrc, zsrc; dt=dt, t=t, nsrc=4)
(3) Read source and receiver geometries from SEG-Y file:
using SegyIO
seis_block = segy_read("test_file.segy")
rec_geometry = Geometry(seis_block; key="receiver", segy_depth_key="RecGroupElevation")
src_geometry = Geometry(seis_block; key="source", segy_depth_key="SourceDepth")
Check the seis_block's header entries to findall out which keywords contain the depth coordinates.
The source depth keyword is either `SourceDepth` or `SourceSurfaceElevation`. The receiver depth
keyword is typically `RecGroupElevation`.
(4) Read source and receiver geometries from out-of-core SEG-Y files (for large data sets). Returns an out-of-core
geometry object `GeometryOOC` without the source/receiver coordinates, but a lookup table instead:
using SegyIO
seis_container = segy_scan("/path/to/data/directory","filenames",["GroupX","GroupY","RecGroupElevation","SourceDepth","dt"])
rec_geometry = Geometry(seis_container; key="receiver", segy_depth_key="RecGroupElevation")
src_geometry = Geometry(seis_container; key="source", segy_depth_key="SourceDepth")
"""
function Geometry(xloc, yloc, zloc; dt=[], t=[], nsrc=nothing)
if any(typeof(x) <: AbstractRange for x=[xloc, yloc, zloc])
args = [typeof(x) <: AbstractRange ? collect(x) : x for x=[xloc, yloc, zloc]]
isnothing(nsrc) && (return Geometry(args...; dt=dt, t=t))
return Geometry(args...; dt=dt, t=t, nsrc=nsrc)
end
isnothing(nsrc) && (return Geometry(tof32(xloc), tof32(yloc), tof32(zloc); dt=dt, t=t))
return Geometry(tof32(xloc), tof32(yloc), tof32(zloc); dt=dt, t=t, nsrc=nsrc)
end
Geometry(xloc::CoordT, yloc::CoordT, zloc::CoordT, dt::Array{T,1}, nt::Array{Integer,1}, t::Array{T,1}) where {T<:Real} = GeometryIC{T}(xloc,yloc,zloc,dt,nt,t)
# Fallback constructors for non standard input types
# Constructor if nt is not passed
function Geometry(xloc::Array{Array{T, 1},1}, yloc::CoordT, zloc::Array{Array{T, 1},1};dt=[],t=[]) where {T<:Real}
nsrc = length(xloc)
# Check if single dt was passed
dtCell = typeof(dt) <: Real ? [T(dt) for j=1:nsrc] : T.(dt)
# Check if single t was passed
tCell = typeof(t) <: Real ? [T(t) for j=1:nsrc] : T.(t)
# Calculate number of time steps
ntCell = floor.(Int, tCell ./ dtCell) .+ 1
return GeometryIC{T}(xloc, yloc, zloc, dtCell, ntCell, tCell)
end
# Constructor if coordinates are not passed as a cell arrays
function Geometry(xloc::Array{T, 1}, yloc::CoordT, zloc::Array{T, 1}; dt=[], t=[], nsrc::Integer=1) where {T<:Real}
xlocCell = [xloc for j=1:nsrc]
ylocCell = [yloc for j=1:nsrc]
zlocCell = [zloc for j=1:nsrc]
dtCell = [T(dt) for j=1:nsrc]
tCell = [T(t) for j=1:nsrc]
ntCell = floor.(Int, tCell ./ dtCell) .+ 1
return GeometryIC{T}(xlocCell, ylocCell, zlocCell, dtCell, ntCell, tCell)
end
################################################ Constructors from SEGY data ####################################################
# Utility function to prepare dtCell, ntCell, tCell from SEGY or based on user defined dt and t.
# Useful when creating geometry for Forward Modeling with custom timings.
_get_p(v, S, nsrc, P) = throw("User defined `dt` is neither: Real, Array of Real or the length of Array doesn't match the number of sources in SEGY")
_get_p(::Nothing, S::SeisBlock, nsrc::Integer, p, ::Type{T}, s::T) where T = fill(T(get_header(S, p)[1]/s), nsrc)
_get_p(::Nothing, S::SeisCon, nsrc::Integer, p, ::Type{T}, s::T) where T = [T(_get_p_SeisCon(S, p, j)/s) for j=1:nsrc]
_get_p(::Nothing, S::Vector{SeisCon}, nsrc::Integer, p, ::Type{T}, s::T) where T = [T(_get_p_SeisCon(S[j], p, 1)/s) for j=1:nsrc]
_get_p(v::Real, data, nsrc::Integer, p, ::Type{T}, s::T) where T = fill(T(v), nsrc)
_get_p(v::Vector, data, nsrc::Integer, p, ::Type{T}, s::T) where T = convert(Vector{T}, v)
_get_p_SeisCon(S::SeisCon, p::String, b::Integer) = try S.blocks[b].summary[p][1] catch; getfield(S, Symbol(p)); end
function timings_from_segy(data, dt=nothing, t::T=nothing) where T
# Get nsrc
nsrc = get_nsrc(data)
dtCell = _get_p(dt, data, nsrc, "dt", Float32, 1f3)
try
tCell = T <: Number ? fill(Float32(t), nsrc) : convert(Vector{Float32}, t)
ntCell = floor.(Integer, tCell ./ dtCell) .+ 1
return dtCell, ntCell, tCell
catch e
ntCell = _get_p(nothing, data, nsrc, "ns", Int, 1)
tCell = Float32.((ntCell .- 1) .* dtCell)
return dtCell, ntCell, tCell
end
end
# Set up source geometry object from in-core data container
function Geometry(data::SegyIO.SeisBlock; key="source", segy_depth_key="", dt=nothing, t=nothing)
src = get_header(data,"FieldRecord")
usrc = unique(src)
nsrc = length(usrc)
if key=="source"
isempty(segy_depth_key) && (segy_depth_key="SourceSurfaceElevation")
params = ["SourceX","SourceY",segy_depth_key]
gt = Float32
elseif key=="receiver"
isempty(segy_depth_key) && (segy_depth_key="RecGroupElevation")
params = ["GroupX","GroupY",segy_depth_key]
gt = Array{Float32, 1}
else
throw("Specified keyword not supported")
end
xloc = Array{gt, 1}(undef, nsrc)
yloc = Array{gt, 1}(undef, nsrc)
zloc = Array{gt, 1}(undef, nsrc)
xloc_full = get_header(data, params[1])
yloc_full = get_header(data, params[2])
zloc_full = get_header(data, params[3])
for j=1:nsrc
traces = findall(src .== usrc[j])
if key=="source" # assume same source location for all traces within one shot record
xloc[j] = convert(gt, xloc_full[traces][1])
yloc[j] = convert(gt, yloc_full[traces][1])
zloc[j] = abs.(convert(gt, zloc_full[traces][1]))
else
xloc[j] = convert(gt, xloc_full[traces])
yloc[j] = convert(gt, yloc_full[traces])
zloc[j] = abs.(convert(gt, zloc_full[traces]))
end
end
if key == "source"
xloc = convertToCell(xloc)
yloc = convertToCell(yloc)
zloc = convertToCell(zloc)
end
dtCell, ntCell, tCell = timings_from_segy(data, dt, t)
return GeometryIC{Float32}(xloc,yloc,zloc,dtCell,ntCell,tCell)
end
# Set up geometry summary from out-of-core data container
function Geometry(data::SegyIO.SeisCon; key="source", segy_depth_key="", dt=nothing, t=nothing)
if key=="source"
isempty(segy_depth_key) && (segy_depth_key="SourceSurfaceElevation")
elseif key=="receiver"
isempty(segy_depth_key) && (segy_depth_key="RecGroupElevation")
else
throw("Specified keyword not supported")
end
# read either source or receiver geometry
nsrc = length(data)
container = Array{SegyIO.SeisCon}(undef, nsrc)
nrec = Array{Integer}(undef, nsrc)
for j=1:nsrc
container[j] = split(data,j)
nrec[j] = key=="source" ? 1 : Int((data.blocks[j].endbyte - data.blocks[j].startbyte)/(240 + data.ns*4))
end
dtCell,ntCell,tCell = timings_from_segy(data, dt, t)
return GeometryOOC{Float32}(container,dtCell,ntCell,tCell,nrec,key,segy_depth_key)
end
# Set up geometry summary from out-of-core data container passed as cell array
function Geometry(data::Array{SegyIO.SeisCon,1}; key="source", segy_depth_key="", dt=nothing, t=nothing)
if key=="source"
isempty(segy_depth_key) && (segy_depth_key="SourceSurfaceElevation")
elseif key=="receiver"
isempty(segy_depth_key) && (segy_depth_key="RecGroupElevation")
else
throw("Specified keyword not supported")
end
nsrc = length(data)
container = Array{SegyIO.SeisCon}(undef, nsrc)
nrec = Array{Integer}(undef, nsrc)
for j=1:nsrc
container[j] = data[j]
nrec[j] = key=="source" ? 1 : Int((data[j].blocks[1].endbyte - data[j].blocks[1].startbyte)/(240 + data[j].ns*4))
end
dtCell,ntCell,tCell = timings_from_segy(data, dt, t)
return GeometryOOC{Float32}(container,dtCell,ntCell,tCell,nrec,key,segy_depth_key)
end
# Load geometry from out-of-core Geometry container
function Geometry(geometry::GeometryOOC)
nsrc = length(geometry.container)
# read either source or receiver geometry
if geometry.key=="source"
params = ["SourceX","SourceY",geometry.segy_depth_key,"dt","ns"]
gt = Float32
elseif geometry.key=="receiver"
params = ["GroupX","GroupY",geometry.segy_depth_key,"dt","ns"]
gt = Array{Float32, 1}
else
throw("Specified keyword not supported")
end
xloc = Array{gt, 1}(undef, nsrc)
yloc = Array{gt, 1}(undef, nsrc)
zloc = Array{gt, 1}(undef, nsrc)
dt = Array{Float32}(undef, nsrc)
nt = Array{Integer}(undef, nsrc)
t = Array{Float32}(undef, nsrc)
for j=1:nsrc
header = read_con_headers(geometry.container[j], params, 1)
if geometry.key=="source"
xloc[j] = convert(gt, get_header(header, params[1])[1])
yloc[j] = convert(gt, get_header(header, params[2])[1])
zloc[j] = abs.(convert(gt,get_header(header, params[3])[1]))
else
xloc[j] = convert(gt, get_header(header, params[1]))
yloc[j] = convert(gt, get_header(header, params[2]))
zloc[j] = abs.(convert(gt, get_header(header, params[3])))
end
dt[j] = geometry.dt[j]
nt[j] = geometry.nt[j]
t[j] = geometry.t[j]
end
if geometry.key == "source"
xloc = convertToCell(xloc)
yloc = convertToCell(yloc)
zloc = convertToCell(zloc)
end
return GeometryIC(xloc,yloc,zloc,dt,nt,t)
end
Geometry(geometry::GeometryIC) = geometry
Geometry(v::Array{T}) where T = v
Geometry(::Nothing) = nothing
###########################################################################################################################################
subsample(g::Geometry, I) = getindex(g, I)
# getindex in-core geometry structure
function getindex(geometry::GeometryIC{T}, srcnum::RangeOrVec) where T
xsub = geometry.xloc[srcnum]
ysub = geometry.yloc[srcnum]
zsub = geometry.zloc[srcnum]
dtsub = geometry.dt[srcnum]
ntsub = geometry.nt[srcnum]
tsub = geometry.t[srcnum]
geometry = GeometryIC{T}(xsub, ysub, zsub, dtsub, ntsub, tsub)
return geometry
end
getindex(geometry::GeometryIC{T}, srcnum::Integer) where T = getindex(geometry, srcnum:srcnum)
# getindex out-of-core geometry structure
getindex(geometry::GeometryOOC, srcnum) = Geometry(geometry.container[srcnum]; key=geometry.key, segy_depth_key=geometry.segy_depth_key, dt=geometry.dt[srcnum], t=geometry.t[srcnum])
# Compare if geometries match
function compareGeometry(geometry_A::Geometry, geometry_B::Geometry)
if isequal(geometry_A.xloc, geometry_B.xloc) && isequal(geometry_A.yloc, geometry_B.yloc) && isequal(geometry_A.zloc, geometry_B.zloc) &&
isequal(geometry_A.dt, geometry_B.dt) && isequal(geometry_A.nt, geometry_B.nt)
return true
else
return false
end
end
==(geometry_A::Geometry, geometry_B::Geometry) = compareGeometry(geometry_A, geometry_B)
isapprox(geometry_A::Geometry, geometry_B::Geometry; kw...) = compareGeometry(geometry_A, geometry_B)
function compareGeometry(geometry_A::GeometryOOC, geometry_B::GeometryOOC)
check = true
for j=1:length(geometry_A.container)
if ~isequal(geometry_A.container[j].blocks[1].summary["GroupX"], geometry_B.container[j].blocks[1].summary["GroupX"]) ||
~isequal(geometry_A.container[j].blocks[1].summary["GroupY"], geometry_B.container[j].blocks[1].summary["GroupY"]) ||
~isequal(geometry_A.container[j].blocks[1].summary["SourceX"], geometry_B.container[j].blocks[1].summary["SourceX"]) ||
~isequal(geometry_A.container[j].blocks[1].summary["SourceY"], geometry_B.container[j].blocks[1].summary["SourceY"]) ||
~isequal(geometry_A.container[j].blocks[1].summary["dt"], geometry_B.container[j].blocks[1].summary["dt"])
check = false
end
end
return check
end
==(geometry_A::GeometryOOC, geometry_B::GeometryOOC) = compareGeometry(geometry_A, geometry_B)
compareGeometry(geometry_A::GeometryOOC, geometry_B::Geometry) = true
compareGeometry(geometry_A::Geometry, geometry_B::GeometryOOC) = true
for G in [GeometryOOC, GeometryIC]
@eval function push!(G1::$G, G2::$G)
for k in fieldnames($G)
pushfield!(getfield(G1, k), getfield(G2, k))
end
end
end
pushfield!(a::Array, b::Array) = append!(a, b)
pushfield!(a, b) = nothing
# Gets called by judiVector constructor to be sure that geometry is consistent with the data.
# Data may be any of: Array, Array of Array, SeisBlock, SeisCon
check_geom(geom::Geometry, data::Array{T}) where T = all([check_geom(geom[s], data[s]) for s=1:get_nsrc(geom)])
check_geom(geom::Geometry, data::Array{T}) where {T<:Number} = _check_geom(geom.nt[1], size(data, 1))
check_geom(geom::Geometry, data::SeisBlock) = _check_geom(geom.nt[1], data.fileheader.bfh.ns)
check_geom(geom::Geometry, data::SeisCon) = _check_geom(geom.nt[1], data.ns)
_check_geom(nt::Integer, ns::Integer) = nt == ns || _geom_missmatch(nt, ns)
_geom_missmatch(nt::Integer, ns::Integer) = throw(judiMultiSourceException("Geometry's number of samples doesn't match the data: $(nt), $(ns)"))