This commit is contained in:
wassname
2017-06-12 09:31:00 +08:00
commit 627ce3e5a1
15 changed files with 71983 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
notes/*
secrets/*
.awspublish-*
# Created by https://www.gitignore.io/api/node,ipythonnotebook,linux,windows,bower
### Node ###
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
### IPythonNotebook ###
# Temporary data
.ipynb_checkpoints/
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Bower ###
bower_components
.bower-cache
.bower-registry
.bower-tmp
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

+22
View File
@@ -0,0 +1,22 @@
{
"name": "js-seis-interp",
"version": "0.0.1",
"description": "Seismic interp in javascript",
"main": "index.js",
"dependencies": {},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "http-server src -oi"
},
"author": "wassname",
"license": "MIT",
"devDependencies": {
"fs": "0.0.2",
"concurrent-transform": "^1.0.0",
"gulp-awspublish": "^3.0.1",
"gulp-debug": "^2.1.2",
"gulp-rename": "^1.2.2",
"gulp-util": "^3.0.7"
}
}
+7
View File
@@ -0,0 +1,7 @@
Interpret a synthetic seismic line so you can measure and improve your error.
Demo at [train-seismic-interp.wassname.com](http://train-seismic-interp.wassname.com/)
![](docs/screenshot.png)
Seismic data source: "The Marmousi2 Model, Elastic Syntrain-seismic-interpthetic Data, and an Analysis of Imaging and AVO in a Structurally Complex Environment", Gary Martin, 2004, Thesis.
+110
View File
@@ -0,0 +1,110 @@
/* This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/). */
/* CSS specs as packaged in the D3 library (d3js.org). Please see license at http://colorbrewer.org/export/LICENSE.txt */
.Dark2 svg .q0-3{stroke:rgb(27,158,119)}
.Dark2 svg .q1-3{stroke:rgb(217,95,2)}
.Dark2 svg .q2-3{stroke:rgb(117,112,179)}
.Dark2 svg .q0-4{stroke:rgb(27,158,119)}
.Dark2 svg .q1-4{stroke:rgb(217,95,2)}
.Dark2 svg .q2-4{stroke:rgb(117,112,179)}
.Dark2 svg .q3-4{stroke:rgb(231,41,138)}
.Dark2 svg .q0-5{stroke:rgb(27,158,119)}
.Dark2 svg .q1-5{stroke:rgb(217,95,2)}
.Dark2 svg .q2-5{stroke:rgb(117,112,179)}
.Dark2 svg .q3-5{stroke:rgb(231,41,138)}
.Dark2 svg .q4-5{stroke:rgb(102,166,30)}
.Dark2 svg .q0-6{stroke:rgb(27,158,119)}
.Dark2 svg .q1-6{stroke:rgb(217,95,2)}
.Dark2 svg .q2-6{stroke:rgb(117,112,179)}
.Dark2 svg .q3-6{stroke:rgb(231,41,138)}
.Dark2 svg .q4-6{stroke:rgb(102,166,30)}
.Dark2 svg .q5-6{stroke:rgb(230,171,2)}
.Dark2 svg .q0-7{stroke:rgb(27,158,119)}
.Dark2 svg .q1-7{stroke:rgb(217,95,2)}
.Dark2 svg .q2-7{stroke:rgb(117,112,179)}
.Dark2 svg .q3-7{stroke:rgb(231,41,138)}
.Dark2 svg .q4-7{stroke:rgb(102,166,30)}
.Dark2 svg .q5-7{stroke:rgb(230,171,2)}
.Dark2 svg .q6-7{stroke:rgb(166,118,29)}
.Dark2 svg .q0-8{stroke:rgb(27,158,119)}
.Dark2 svg .q1-8{stroke:rgb(217,95,2)}
.Dark2 svg .q2-8{stroke:rgb(117,112,179)}
.Dark2 svg .q3-8{stroke:rgb(231,41,138)}
.Dark2 svg .q4-8{stroke:rgb(102,166,30)}
.Dark2 svg .q5-8{stroke:rgb(230,171,2)}
.Dark2 svg .q6-8{stroke:rgb(166,118,29)}
.Dark2 svg .q7-8{stroke:rgb(102,102,102)}
.Paired svg .q0-3{stroke:rgb(166,206,227)}
.Paired svg .q1-3{stroke:rgb(31,120,180)}
.Paired svg .q2-3{stroke:rgb(178,223,138)}
.Paired svg .q0-4{stroke:rgb(166,206,227)}
.Paired svg .q1-4{stroke:rgb(31,120,180)}
.Paired svg .q2-4{stroke:rgb(178,223,138)}
.Paired svg .q3-4{stroke:rgb(51,160,44)}
.Paired svg .q0-5{stroke:rgb(166,206,227)}
.Paired svg .q1-5{stroke:rgb(31,120,180)}
.Paired svg .q2-5{stroke:rgb(178,223,138)}
.Paired svg .q3-5{stroke:rgb(51,160,44)}
.Paired svg .q4-5{stroke:rgb(251,154,153)}
.Paired svg .q0-6{stroke:rgb(166,206,227)}
.Paired svg .q1-6{stroke:rgb(31,120,180)}
.Paired svg .q2-6{stroke:rgb(178,223,138)}
.Paired svg .q3-6{stroke:rgb(51,160,44)}
.Paired svg .q4-6{stroke:rgb(251,154,153)}
.Paired svg .q5-6{stroke:rgb(227,26,28)}
.Paired svg .q0-7{stroke:rgb(166,206,227)}
.Paired svg .q1-7{stroke:rgb(31,120,180)}
.Paired svg .q2-7{stroke:rgb(178,223,138)}
.Paired svg .q3-7{stroke:rgb(51,160,44)}
.Paired svg .q4-7{stroke:rgb(251,154,153)}
.Paired svg .q5-7{stroke:rgb(227,26,28)}
.Paired svg .q6-7{stroke:rgb(253,191,111)}
.Paired svg .q0-8{stroke:rgb(166,206,227)}
.Paired svg .q1-8{stroke:rgb(31,120,180)}
.Paired svg .q2-8{stroke:rgb(178,223,138)}
.Paired svg .q3-8{stroke:rgb(51,160,44)}
.Paired svg .q4-8{stroke:rgb(251,154,153)}
.Paired svg .q5-8{stroke:rgb(227,26,28)}
.Paired svg .q6-8{stroke:rgb(253,191,111)}
.Paired svg .q7-8{stroke:rgb(255,127,0)}
.Paired svg .q0-9{stroke:rgb(166,206,227)}
.Paired svg .q1-9{stroke:rgb(31,120,180)}
.Paired svg .q2-9{stroke:rgb(178,223,138)}
.Paired svg .q3-9{stroke:rgb(51,160,44)}
.Paired svg .q4-9{stroke:rgb(251,154,153)}
.Paired svg .q5-9{stroke:rgb(227,26,28)}
.Paired svg .q6-9{stroke:rgb(253,191,111)}
.Paired svg .q7-9{stroke:rgb(255,127,0)}
.Paired svg .q8-9{stroke:rgb(202,178,214)}
.Paired svg .q0-10{stroke:rgb(166,206,227)}
.Paired svg .q1-10{stroke:rgb(31,120,180)}
.Paired svg .q2-10{stroke:rgb(178,223,138)}
.Paired svg .q3-10{stroke:rgb(51,160,44)}
.Paired svg .q4-10{stroke:rgb(251,154,153)}
.Paired svg .q5-10{stroke:rgb(227,26,28)}
.Paired svg .q6-10{stroke:rgb(253,191,111)}
.Paired svg .q7-10{stroke:rgb(255,127,0)}
.Paired svg .q8-10{stroke:rgb(202,178,214)}
.Paired svg .q9-10{stroke:rgb(106,61,154)}
.Paired svg .q0-11{stroke:rgb(166,206,227)}
.Paired svg .q1-11{stroke:rgb(31,120,180)}
.Paired svg .q2-11{stroke:rgb(178,223,138)}
.Paired svg .q3-11{stroke:rgb(51,160,44)}
.Paired svg .q4-11{stroke:rgb(251,154,153)}
.Paired svg .q5-11{stroke:rgb(227,26,28)}
.Paired svg .q6-11{stroke:rgb(253,191,111)}
.Paired svg .q7-11{stroke:rgb(255,127,0)}
.Paired svg .q8-11{stroke:rgb(202,178,214)}
.Paired svg .q9-11{stroke:rgb(106,61,154)}
.Paired svg .q10-11{stroke:rgb(255,255,153)}
.Paired svg .q0-12{stroke:rgb(166,206,227)}
.Paired svg .q1-12{stroke:rgb(31,120,180)}
.Paired svg .q2-12{stroke:rgb(178,223,138)}
.Paired svg .q3-12{stroke:rgb(51,160,44)}
.Paired svg .q4-12{stroke:rgb(251,154,153)}
.Paired svg .q5-12{stroke:rgb(227,26,28)}
.Paired svg .q6-12{stroke:rgb(253,191,111)}
.Paired svg .q7-12{stroke:rgb(255,127,0)}
.Paired svg .q8-12{stroke:rgb(202,178,214)}
.Paired svg .q9-12{stroke:rgb(106,61,154)}
.Paired svg .q10-12{stroke:rgb(255,255,153)}
.Paired svg .q11-12{stroke:rgb(177,89,40)}
+66
View File
@@ -0,0 +1,66 @@
.seismic-container svg {
/*border: 1px solid grey;*/
}
.seismic-container {
/* to match error chart */
/*margin-left: 70px;*/
/*margin-right: 50px*/
}
#score {
font-style: italic;
}
#interp {
font-size: small;
display: block;
border: 1px solid grey;
}
svg .line {
stroke-width: 2px;
stroke-linecap: round;
fill: none;
/*opacity: 0.7;*/
}
svg .line.interp {
/**/
}
svg .line.ideal-horizon {
stroke-dasharray: 5,5;
}
/* this show the error between the interp and ideal answer */
svg .line.error {
/*stroke: green;*/
stroke-width: 2px;
stroke-linecap: round;
fill: yellow;
opacity: 0.4;
}
svg .well-trace {
stroke-width: 2;
stroke: red;
fill: none;
}
svg .well .label {}
svg .well-top {
stroke-width: 4px;
opacity: 50%;
/*stroke-dasharray: 1, 5;*/
}
svg .axis path,
svg .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
svg .axis.x .label {}
svg .axis.y .label {}
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+10
View File
@@ -0,0 +1,10 @@
{
"source": "'The Marmousi2 Model, Elastic Synthetic Data, and an Analysis of Imaging and AVO in a Structurally Complex Environment', Gary Martin, 2004, Thesis.",
"model":"MODEL_P-WAVE_VELOCITY_1.25m.png",
"seismic": "SYNTHETIC.png",
"xinterval": 1.25,
"xunit": "m",
"yint": 5,
"yunit": "m",
"horizons": "FINAL_HORIZONS.json"
}
+1
View File
@@ -0,0 +1 @@
Source: "The Marmousi2 Model, Elastic Syntrain-seismic-interpthetic Data, and an Analysis of Imaging and AVO in a Structurally Complex Environment", Gary Martin, 2004, Thesis.
+104
View File
@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>interpret synthetic seismic</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dc/2.1.4/dc.css" />
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/octicons/3.1.0/octicons.min.css"> -->
<link rel="stylesheet" href="css/colorbrewer.css">
<link rel="stylesheet" href="css/main.css">
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<h1>
Click the image to interpret the synthetic seismic
</h1>
<button id="undo">Undo</button>
<!-- <button id="reset">Reset</button> -->
<button id="toggleModel" disabled>Toggle Seismic/VModel </button>
<button id="Finish">Finish</button>
<span style="">-</span>
<div id="svg" class="Paired seismic-container">
</div>
<div class="error-graph">
<div class="" id="interp-error-chart">
</div>
</div>
<!-- Show raw data here -->
<div class="">
<label>Error</label>
<textbox id="score">N/A</textbox>
(meters squared per meter)
</div>
<svg height=110 width=170 id="svg-color-ordinal"></svg>
<div class="">
<h2>Instructions</h2>
<ol>
<li>
Click the image to interpret the blue line
</li>
<li>Click finish to see how much error you had</li>
</ol>
<h2>FAQ</h2>
<strong>How do you know where the true horizon is? Isn't it subjective</strong> This is synthetic seismic produced from a velocity model, so we know the real location of horizons, letting us get real feedback on our interpreation. To see the velocity model click "Toggle Seismic/VModel" after you have finished.
</div>
<div class="">
<h4>
Horizon data:
</h4>
<textbox id="interp"></textbox>
</div>
<hr />
<div>
<p>
<small>Synthetic seismic data from <a href="#ref-1">[1]</a></small>
</p>
</div>
<small><h4>References:</h4>
<ol>
<li id="ref-1">"The Marmousi2 Model, Elastic Synthetic Data, and an Analysis of Imaging and AVO in a Structurally Complex Environment", <a href="http://www.agl.uh.edu/downloads/downloads.htm">Gary Martin, 2004, Thesis.</a></li>
</ol></small>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/2.1.4/dc.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.13.0/d3-legend.js"></script>
<!-- app -->
<script src="js/seismic-plot.js"></script>
<script src="js/error-plot.js"></script>
<script src="js/main.js"></script>
</body>
</html>
+167
View File
@@ -0,0 +1,167 @@
function ErrorPlot(id, options){
this.id=id
this.options=_.defaultsDeep(options,{
width: 2721/2, // must preserve aspect ratio
height: 701/2,
margin: {
left:70,
right:130
}
})
this.data=[]
}
ErrorPlot.prototype.init = function(x,y){
this.totalWidth = this.options.margin.left+this.options.width+this.options.margin.right
this.totalHeight = this.options.margin.top+this.options.height+this.options.margin.bottom
this.vis = d3.select(this.id).append("svg")
.attr("width", this.totalWidth)
.attr("height", this.totalHeight);
// create x and y transforms
var {xmin, xmax, ymin, ymax} = this.options.extent
this.x = d3.scale.linear()
.domain([xmin, xmax])
.range([this.options.margin.left, this.options.width+this.options.margin.left]);
this.y = d3.scale.linear()
// this.y = d3.scale.log().clamp([1e-7,1e7]).domain([1e-7, 100])
.domain([-100, 100])
.range([this.options.margin.top, this.options.height+this.options.margin.top]);
// setup axis
this.xAxis = d3.svg.axis().scale(this.x).orient("top");
this.vis
.append("g")
.attr("class", "x axis")
.attr("transform", "translate("+0+"," + this.options.margin.top + ")")
.call(this.xAxis)
// LABEL
.append("text")
.attr("class", "label")
.attr("x", "20em") // y margin
.attr("y", "1em") // xmargin
.attr("dx", ".71em") // additional shift
.style("text-anchor", "start")
.text("Distance (m)");
this.yAxis = d3.svg.axis().scale(this.y).orient("left");
this.vis.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + this.options.margin.left + ","+0+")")
.call(this.yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("x", "-10em")
.attr("y", "1em")
.style("text-anchor", "end")
.text("Error (m)");
this.series = this.vis.append("g")
.attr("class","Paired series")
}
ErrorPlot.prototype.plot = function(x,y){
var self = this
var data = x.map((n,i)=>({
x:n,
y:y[i],
i:i
}))
this.data.push(data)
var allData = _.flatten(this.data)
var yMin = _.min(allData.map(r=>r.y))
var yMax = _.max(allData.map(r=>r.y))
var yMax2 = _.max([-yMin,yMax])
// update y axis https://gist.github.com/phoebebright/3098488
this.y.domain([-yMax2,yMax2])
this.vis.select(".y.axis")
.transition().duration(1500).ease("sin-in-out") // https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease
.call(this.yAxis);
var i = this.series[0][0].children.length
var series = this.series.append("g")
.attr("id",`dots-${i}`)
.attr("class",`dots dots-${i}`)
this.series.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("x", d=>self.x(d.x))
.attr("y", d=>self.y(d.y))
.attr("cx", d=>self.x(d.x))
.attr("cy", d=>self.y(d.y))
.attr("class",`q${i}-12`)
// .style("fill", function(d) { return color(d.species); });
}
ErrorPlot.prototype.legend = function(x,y){
}
ErrorPlot.prototype.clear = function(x,y){
this.vis.selectAll(".dot").clear()
this.data=[]
}
//
// function errorPlot(x,y,id, options){
//
// options=_.defaultsDeep(options,{
// width: 2721/2, // must preserve aspect ratio
// height: 701/2,
// margin: {
// left:70,
// right:130
// }
// })
//
// // format dc data and groups
// var data = x.map((n,i)=>({
// x:n,
// y:y[i],
// i:i
// }))
// var ndx = crossfilter(data);
// var errorDim = ndx.dimension(d=>([d.x,d.y]))
// var errorGroup = errorDim.group()
//
// // work out axis lengths
// var xMax = _.max(data.map(r=>r.x))
// var yMin = _.min(data.map(r=>r.y))
// var yMax = _.min(data.map(r=>r.y))
// var yMax2=_.max([yMax,-yMin]) || 100
//
// // this is a HACK to render an empty graph when y values are null
// var symbolSize = yMin==undefined?0:8
//
// // http://dc-js.github.io/dc.js/examples/scatter.html
// errorChart = dc.scatterPlot(id);
// errorChart
// .width(options.width+options.margin.left+options.margin.right)
// .height(options.height/2)
// .x(d3.scale.linear().domain([0, xMax]))
// .y(d3.scale.linear().domain([-yMax2,yMax2]))
// .brushOn(false)
// .symbolSize(symbolSize)
// .clipPadding(10)
// .yAxisLabel("Error (m)")
// .xAxisLabel("Length (m)")
// .dimension(errorDim)
// .group(errorGroup);
// errorChart.margins().left=options.margin.left
// errorChart.margins().right=options.margin.right
// errorChart.margins().top=0
// errorChart.margins().bottom=0
//
// errorChart.render();
// }
+80
View File
@@ -0,0 +1,80 @@
// list of horizons by length
// var horizons_whitelist = ['H089-UC', 'Fault1', 'H042', 'H033', 'Fault2', 'H046', 'H038', 'H039', 'H045', 'H064', 'H047', 'H037', 'H048', 'H053', 'H050', 'H051', 'H052', 'H044', 'H034', 'H032', 'H049', 'H065', 'H068', 'H069', 'H070', 'Fault3', 'H029', 'H035', 'H031', 'H036', 'H030', 'H040', 'H077', 'H027', 'H072', 'H078', 'H079', 'H003', 'H028', 'H088_Top_Salt', 'H025', 'H021', 'H061', 'H023', 'H022', 'H080', 'H062', 'H024', 'H013', 'H020', 'H019', 'H017', 'H054', 'H056', 'H015', 'H057', 'H016', 'H018', 'H055', 'H176', 'H059', 'H058', 'H060', 'H175', 'H014', 'H012', 'H026', 'H071', 'H011', 'H063', 'H073', 'H067', 'H081', 'H174', 'H066', 'H182', 'H007', 'H183', 'H082', 'H074', 'H008', 'H173', 'H009', 'H010', 'H181', 'H172', 'H083', 'H184', 'H180', 'H075', 'H084', 'H179', 'H171', 'H005', 'H076', 'H185', 'H159', 'H170', 'H186', 'H169', 'H160-UC', 'H157', 'H168', 'H158', 'H156', 'H085', 'H187', 'H041_levee', 'H006', 'H086', 'H188', 'H155', 'H087', 'H154', 'H153', 'H152', 'H189', 'H151', 'H177', 'H150', 'H043_channel', 'H162', 'H167', 'H004', 'H149', 'H148', 'H190', 'H178', 'H147', 'H139', 'H142', 'H161', 'H055-056_noAIp', 'H144', 'H143', 'H145', 'H140', 'H138', 'H166', 'H141', 'H163', 'H137', 'H146', 'H128', 'H136', 'H130', 'H134', 'H191', 'H135', 'H129', 'H165', 'H132', 'H133', 'H164', 'H121', 'H131', 'H124', 'H127', 'H125', 'H126', 'H123', 'H122', 'H119', 'H093', 'H120', 'H101', 'H102', 'H104', 'H103', 'H097', 'H092', 'H115', 'H107', 'H105', 'H118', 'H109', 'H094', 'H108', 'H112', 'H095', 'H106', 'H099', 'H113', 'H096', 'H116', 'H111', 'H098', 'H100', 'H110', 'H117', 'H091', 'H114', 'H090', 'H050-051_OIL', 'H002', 'H052-053_OIL', 'H001-wb', 'H048-049_OIL', 'H070-071_OIL']
/**
* Interp one array onto another like scipys interp1d
* @param {Array} a - array of objects e.g. [{x:1,y:2},{x:2,y:1.5}]
* @return {function} - an interpolation function: yNew = f(xNew)
*/
function interp1d(a){
var aSorted = _.sortBy(a, ar=>ar.x)
aSorted = _.uniqBy(aSorted,ar=>ar.x)
/** yNew = f(xNew) **/
return function(xNew){
return xNew.map(xn => {
// find item before and after
var i = _.sortedIndexBy(aSorted, {x:xn}, a=>a.x)
i = _.clamp(i,1,aSorted.length-1)
var a0 = aSorted[i-1]
var a1 = aSorted[i]
// work out where xn fits between a0 and a1 as a fraction
var xnFrac = (xn-a0.x)/(a1.x-a0.x)
return d3.interpolateNumber(a0.y,a1.y)(xnFrac)
})
}
}
// TODO test
var interp1dTestInput = [{"x":864.6820640353673,"y":1978.4236804564907}, {"x":864.6820640353673,"y":1978.4236804564907},{"x":3238.809413832805,"y":1598.965763195435},{"x":6562.587436618545,"y":2118.2239657631953},{"x":6562.587436618545,"y":2118.2239657631953},{"x":9586.475712987527,"y":1239.4793152639086}]
var result = interp1d(interp1dTestInput)([-1,100,3333,90000])
var idealResult = [2116.786061504474, 2100.6431828814566, 1613.6807153181974, -22128.763105338083]
console.assert(_.isEqual(result, idealResult),'interp1d should work for predefined inputs')
var dataPath = 'data/Marmousi2';
d3.json(dataPath+'/metadata.json', function(error, metadata){
if (error) console.error(error)
d3.json(dataPath+'/'+metadata.horizons, function(error, horizons){
if (error) console.error(error)
// TODO legend, well labels, show joints,
var errorChart
var seismicPlot
var filtered_horizons = _.filter(horizons,horizon=>['H037'].includes(horizon.name))
$(document).ready(function () {
seismicPlot = new SeismicPlot({
model: dataPath+'/'+metadata.model,
seismic: dataPath+'/'+metadata.seismic,
horizons: filtered_horizons,
onmousedown: (obj)=>{
document.querySelectorAll('#interp')[0].innerText = JSON.stringify(seismicPlot.interp, '\n')
}
})
seismicPlot.init()
seismicPlot.showWells(5)
d3.select('#undo').on('click', ()=>seismicPlot.undo())
d3.select('#reset').on('click', ()=>seismicPlot.reset())
d3.select('#toggleModel').on('click', ()=>seismicPlot.toggleModel())
d3.select('#Finish').on('click', ()=>{
var errors=seismicPlot.finish()
var meanError = _.mean(_.flatten(errors.map(r=>r.y)))
var formatter = d3.format('.2f')
document.querySelectorAll('#score')[0].innerText = ''+formatter(meanError)
errors.map(r=>errorPlot.plot(r.x,r.y,r.name))
})
// make empty error plot
var x = _.flatten(_.values(seismicPlot.options.horizons[0].data)).reverse().map(r=>r.x)
var errorPlot = new ErrorPlot('#interp-error-chart',seismicPlot.options)
errorPlot.init()
})
})})
+363
View File
@@ -0,0 +1,363 @@
/**
* Class to display a seismic line and let a user interp on it
*/
function SeismicPlot(options) {
this.options=_.defaultsDeep(options,{
width: 2721/2, // must preserve aspect ratio
height: 701/2,
wellFractions: [0.1,0.9],
extent:{
xmin:0,
xmax:17000,
ymin:0,
ymax:3500,
},
margin: {
left:70,
right:130,
top:50,
bottom:10
},
model: "data/MODEL_P-WAVE_VELOCITY_1.25m.png",
seismic: 'data/SYNTHETIC.png',
horizons: [],
onmousedown: function(){}, // callback
})
this.interp = []
}
SeismicPlot.prototype.init = function (arguments) {
var self = this;
this.totalWidth = this.options.margin.left+this.options.width+this.options.margin.right
this.totalHeight = this.options.margin.top+this.options.height+this.options.margin.bottom
this.vis = d3.select("#svg").append("svg")
.attr("width", this.totalWidth)
.attr("height", this.totalHeight)
.on("mousedown", function(){
self.mousedown(this)
return self.options.onmousedown(this)
});
this.active_interp = 0
// this.formatter = d3.format('.2f')
// create x and y transforms
var {xmin, xmax, ymin, ymax} = this.options.extent
this.x = d3.scale.linear()
.domain([xmin, xmax])
.range([this.options.margin.left, this.options.width+this.options.margin.left]);
this.y = d3.scale.linear()
.domain([ymin, ymax])
.range([this.options.margin.top, this.options.height+this.options.margin.top]);
// line function
this.linef = d3.svg.line()
.x(d => this.x(d.x))
.y(d => this.y(d.y));
// load images
this.seismicImage = this.vis.append("image")
.attr("xlink:href", this.options.seismic)
.attr("id", 'image')
.attr("x",this.options.margin.left)
.attr("y",this.options.margin.top)
.attr("width", this.options.width)
.attr("height", this.options.height)
.attr("preserveAspectRatio","xMinYMin meet");
this.modelImage = this.vis.append("image")
.attr("xlink:href", this.options.model)
.attr("id", 'image')
.attr("x",this.options.margin.left)
.attr("y",this.options.margin.top)
.attr("width", this.options.width)
.attr("height", this.options.height)
.attr("style", 'display:none')
.attr("preserveAspectRatio","xMinYMin meet");
// setup axis
this.xAxis = d3.svg.axis().scale(this.x).orient("top");
this.vis
.append("g")
.attr("class", "x axis")
.attr("transform", "translate("+0+"," + this.options.margin.top + ")")
.call(this.xAxis)
// LABEL
.append("text")
.attr("class", "label")
.attr("x", "20em") // y margin
.attr("y", "1em") // xmargin
.attr("dx", ".71em") // additional shift
.style("text-anchor", "start")
.text("Distance (m)");
this.yAxis = d3.svg.axis().scale(this.y).orient("left");
this.vis.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + this.options.margin.left + ","+0+")")
.call(this.yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("x", "-10em")
.attr("y", "1em")
.style("text-anchor", "end")
.text("Depth (m)");
// add container groups
this.wellsGroup = this.vis.append("g")
.attr("id", `wells`)
this.linesGroup = this.vis.append("g")
.attr("id", `lines`)
this.legendGroup = this.vis.append("g")
.attr("id","legend")
.attr("transform", "translate("+(self.options.margin.left+self.options.width)+","+(this.options.margin.top+70)+")"); // place legend
// button, this should be done outside this class
// d3.select('#undo').on('click', ()=>this.undo())
// d3.select('#reset').on('click', ()=>this.reset())
// d3.select('#toggleModel').on('click', ()=>this.toggleModel())
// d3.select('#Finish').on('click', ()=>this.finish())
this.legend()
}
SeismicPlot.prototype.legend = function () {
var data = _.concat(
this.options.horizons.map((h,i)=>({
text:'horizon '+h.name,
class:`line interp q${i}-12`
})),
this.options.horizons.map((h,i)=>({
text:'well top '+h.name,
class:`line well-top q${i}-12`
})),
this.options.horizons.map((h,i)=>({
text:'answer '+h.name,
class:`line ideal-horizon q${i}-12`
}))
)
// var width = 200 // this.options.width+this.options.margin.left
var dh = 20 // height of each legend element
var padding = 1
var height = this.options.margin.top
var legend = this.legendGroup.selectAll(".legend")
.data(data)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * dh + ")"; });
// add white background
legend.append("rect")
.attr("x", - dh)
.attr("width", dh)
.attr("height", dh)
.style("fill", 'white');
legend.append("path")
// .attr("d", linef([{x:width,y:0},{x:width,y:10}]))
.attr("d", d3.svg.line()([[-dh,dh/2],[0,dh/2]]))
.attr("dy","0.5em")
.attr("dx","0.5em")
.attr("class", d=>d.class)
legend.append("text")
.attr("x", - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d.text; });
}
SeismicPlot.prototype.updateLine = function () {
var i = this.active_interp
// make sure each line has a class
var path = this.vis.select(`#line-${i}`)
if (!path.node()){
path = this.linesGroup.append("path")
.attr("class", `line interp q${i}-12`)
.attr("id", `line-${i}`)
}
path
.transition()
.attr("d", this.linef(this.interp[i]));
// this.interpBox.innerText = JSON.stringify(this.interp, '\n')
}
SeismicPlot.prototype.mousedown = function(context) {
var m = d3.mouse(context);
if (!this.interp[this.active_interp]) this.interp[this.active_interp]=[]
this.interp[this.active_interp].push({
x: this.x.invert(m[0]),
y: this.y.invert(m[1])
})
this.updateLine()
}
SeismicPlot.prototype.undo = function() {
this.interp[this.active_interp].pop()
this.updateLine()
}
SeismicPlot.prototype.reset = function() {
this.interp = []
this.updateLine()
}
SeismicPlot.prototype.toggleModel = function(on) {
if ((on!==undefined && on )|| (on===undefined && this.modelImage.attr("style") == 'display:none'))
this.modelImage.attr("style", '')
else
this.modelImage.attr("style", 'display:none')
}
SeismicPlot.prototype.finish = function(){
// compare interp to ideal horizon, and show area between, turn on fill I guess
// TODO make interp into an array of horizons
var errors = this.interp.map((interp,i)=>{
var horizon = this.options.horizons[i] // HACK just use first for now
var ideal = _.flatten(_.values(horizon.data)).reverse()
var data = _.concat(ideal,this.interp[i])
// join your interp and the ideal to make a polygon, then display with fill
var path = this.linesGroup.append("path")
.attr("class", `line error q${i%11}-12`)
.attr("id", `error-${i}`)
.attr("d", this.linef(data));
var yPred = interp1d(interp)(ideal.map(r=>r.x))
var yTrue = ideal.map(r=>r.y)
var yError = yTrue.map((n,i)=>yPred[i]-n)
// TODO plot error, scatter plot and hist
// bin length
var xLength = yPred.map((a,i)=>{
var yErr = Math.abs(a-yTrue[i])
// half distance between left and right neigbours
var i0= Math.max(i-1,0)
var i1 = Math.min(i+1,yTrue.length-1)
var xLength = Math.abs(ideal[i1].x-ideal[i0].x)/2
return xLength
})
// weight error by bin
var error = _.sum(xLength.map((xLen,i)=>{
var yErr = yError[i]
return yErr*xLen
}))/_.sum(xLength)
var xError = ideal.map(r=>r.x)
return {x:xError,y:yError,name:horizon.name}
})
// this.scoreBox.innerText = ''+this.formatter(_(errors).mean())
this.showHorizons() // show ideal
$('#toggleModel').attr('disabled',false) // let us see model
this.toggleModel(true)
return errors
}
SeismicPlot.prototype.showWells = function(n){
// show horizons at traces at 10 and 90%
// TODO move traces to options
var {xmin, xmax, ymin, ymax} = this.options.extent
let traces = this.options.wellFractions.map(f=>(xmax-xmin)*f)
// TODO add rotated text at well position
// draw lines at each trace
this.wells = traces.map((x0,i)=>{
// group
let wellGroup = this.wellsGroup.append("g").attr("class","well well-"+i)
// well trace
let well = wellGroup.append("line")
.attr("class", "well-trace")
.attr("x1", this.x(x0)) //<<== change your code here
.attr("y1", this.y(ymin))
.attr("x2", this.x(x0)) //<<== and here
.attr("y2", this.y(ymax))
.style("stroke-width", 2)
.style("stroke", "red")
.style("fill", "none");
// label
wellGroup
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("x", -(this.y(ymin)+this.y(ymax))/2) // margin-top
.attr("y", this.x(x0))
.attr("dy", "-0.2em") // margin-left
.style("text-anchor", "start")
.text("Well "+i);
// draw horizons near well
let cutoff = (xmax-xmin)*0.02 // 5%
this.options.horizons.map((horizon,j)=>{
let data = _.flatten(_.values(horizon.data)) // join segments
let segment = data.filter(d=>Math.abs(x0-d.x)<cutoff)
// get the x and y location nearest the well
// plot line
var path = this.linesGroup.append("path")
.attr("class", `line well-top well-${i} top-${j} q${i%11}-12 `)
.attr("id", `well-${i}-top-${j}`)
.attr("d", this.linef(segment));
// add tooltip https://gist.github.com/ilyabo/1339996
title=path.append("svg:title")
.text("Well top: "+horizon.name)
})
})
}
/**
* Show some horizons, n will limit it to n points in the first segment (as a interp prompt)
* TODO make a fake well instead
**/
SeismicPlot.prototype.showHorizons = function(n){
if (n===undefined) n=1E5
this.options.horizons.forEach((horizon,i)=>{
var segments = Object.keys(horizon.data)
if (n<1E5) segments=segments.slice(0,1)
segments.forEach(segment=>{
// plot line
var path = this.linesGroup.append("path")
.attr("class", `line q${i%11}-12 ideal-horizon`)
.attr("id", `ideal-horizon-${i}`)
.attr("d", this.linef(horizon.data[segment].slice(0,n)));
// add tooltip https://gist.github.com/ilyabo/1339996
title=path.append("svg:title")
.text(horizon.name)
})
})
}