|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using Unplugged.IbmBits;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Unplugged.Segy
|
|
|
|
|
|
{
|
|
|
|
|
|
public class ValueRange
|
|
|
|
|
|
{
|
|
|
|
|
|
public float Min { get; set; }
|
|
|
|
|
|
public float Max { get; set; }
|
|
|
|
|
|
public float Delta { get; set; }
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Responsible for reading SEGY files given a path or a Stream.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class SegyReader
|
|
|
|
|
|
{
|
|
|
|
|
|
public ISegyOptions Options { get; set; }
|
|
|
|
|
|
//public int InlineNumberLocation { get; set; }
|
|
|
|
|
|
//public int CrosslineNumberLocation { get; set; }
|
|
|
|
|
|
//public int TraceNumberLocation { get; set; }
|
|
|
|
|
|
//public int SourceXLocation { get; set; }
|
|
|
|
|
|
//public int SourceYLocation { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
public SegyReader()
|
|
|
|
|
|
{
|
|
|
|
|
|
Options = new SegyOptions();
|
|
|
|
|
|
//InlineNumberLocation = 189;
|
|
|
|
|
|
//CrosslineNumberLocation = 193;
|
|
|
|
|
|
|
|
|
|
|
|
//InlineNumberLocation = 0;
|
|
|
|
|
|
//CrosslineNumberLocation = 4;
|
|
|
|
|
|
//TraceNumberLocation = 8;
|
|
|
|
|
|
|
|
|
|
|
|
//SourceXLocation = 72;
|
|
|
|
|
|
//SourceYLocation = 76;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region From the Top: Methods that start reading from the beginning of the file
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given a file path, reads entire SEGY file into memory
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual ISegyFile Read(string path, IReadingProgress progress = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var stream = File.OpenRead(path))
|
|
|
|
|
|
return Read(stream, progress);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given stream, reads entire SEGY file into memory.
|
|
|
|
|
|
/// Assumes the stream is at the start of the file.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual ISegyFile Read(Stream stream, IReadingProgress progress = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Read(stream, int.MaxValue, progress);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given stream and traceCount, reads the requested number
|
|
|
|
|
|
/// of traces into memory. The given traceCount may exceed
|
|
|
|
|
|
/// the number of traces in the file;
|
|
|
|
|
|
/// in that case all the traces in the file are read.
|
|
|
|
|
|
/// Assumes the stream is at the start of the file.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual ISegyFile Read(Stream stream, int traceCount, IReadingProgress progress = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
SegyFile segyFile = new SegyFile();
|
|
|
|
|
|
using (var reader = new BinaryReader(stream))
|
|
|
|
|
|
{
|
|
|
|
|
|
var fileHeader = ReadFileHeader(reader);
|
|
|
|
|
|
var traces = new List<ITrace>();
|
|
|
|
|
|
for (int i = 0; i < traceCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (progress != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// TODO: Check if stream.Length breaks when streaming from web
|
|
|
|
|
|
int percentage = (int)(100 * stream.Position / stream.Length);
|
|
|
|
|
|
progress.ReportProgress(percentage);
|
|
|
|
|
|
if (progress.CancellationPending)
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
var trace = ReadTrace(reader, fileHeader.SampleFormat, fileHeader.IsLittleEndian);
|
|
|
|
|
|
if (trace == null)
|
|
|
|
|
|
break;
|
|
|
|
|
|
traces.Add(trace);
|
|
|
|
|
|
}
|
|
|
|
|
|
segyFile.Header = fileHeader;
|
|
|
|
|
|
segyFile.Traces = traces;
|
|
|
|
|
|
return segyFile;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given a BinaryReader, reads the SEGY File Header into memory.
|
|
|
|
|
|
/// Asummes the BinaryReader is at the start of the file.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual IFileHeader ReadFileHeader(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
var text = ReadTextHeader(reader);
|
|
|
|
|
|
FileHeader header = ReadBinaryHeader(reader) as FileHeader;
|
|
|
|
|
|
header.Text = text;
|
|
|
|
|
|
return header;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given a file path reads the text header from the beginning
|
|
|
|
|
|
/// of the SEGY file.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual string ReadTextHeader(string path)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var stream = File.OpenRead(path))
|
|
|
|
|
|
return ReadTextHeader(stream);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given a stream reads the text header.
|
|
|
|
|
|
/// Assumes the stream is at the start of the file.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual string ReadTextHeader(Stream stream)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var reader = new BinaryReader(stream))
|
|
|
|
|
|
return ReadTextHeader(reader);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given a BinaryReader reads the text header.
|
|
|
|
|
|
/// Assumes the BinaryReader is at the start of the file.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual string ReadTextHeader(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
var textHeaderLength = Options.TextHeaderColumnCount * Options.TextHeaderRowCount;
|
|
|
|
|
|
var bytes = reader.ReadBytes(textHeaderLength);
|
|
|
|
|
|
string text = (bytes[0] == 'C') || Options.IsEbcdic == false ?
|
|
|
|
|
|
ASCIIEncoding.Default.GetString(bytes) :
|
|
|
|
|
|
IbmConverter.ToString(bytes);
|
|
|
|
|
|
return Options.TextHeaderInsertNewLines ? InsertNewLines(text) : text;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Already in progress: Methods that start reading from the current location in the stream
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given a BinaryReader, reads the binary header.
|
|
|
|
|
|
/// Assumes that the binary header is the next item to be read.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual IFileHeader ReadBinaryHeader(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
FileHeader fileHeader = new FileHeader();
|
|
|
|
|
|
var binaryHeader = reader.ReadBytes(_binaryHeaderSize);
|
|
|
|
|
|
var byte0 = binaryHeader[_sampleFormatIndex];
|
|
|
|
|
|
var byte1 = binaryHeader[_sampleFormatIndex + 1];
|
|
|
|
|
|
bool isLittleEndian = (byte1 == 0);
|
|
|
|
|
|
var sampleFormat = isLittleEndian ?
|
|
|
|
|
|
(FormatCode)byte0 :
|
|
|
|
|
|
(FormatCode)byte1;
|
|
|
|
|
|
byte[] bts = new byte[2];
|
|
|
|
|
|
bts[0]=binaryHeader[17]; bts[1]=binaryHeader[16];
|
|
|
|
|
|
int nSampleInterval = BitConverter.ToInt16(bts, 0);
|
|
|
|
|
|
bts[0]=binaryHeader[21]; bts[1]=binaryHeader[20];
|
|
|
|
|
|
int nSampleNumber = BitConverter.ToInt16(bts, 0);
|
|
|
|
|
|
fileHeader.SampleFormat = sampleFormat;
|
|
|
|
|
|
fileHeader.IsLittleEndian = isLittleEndian;
|
|
|
|
|
|
this.Options.IsLittleEndian = isLittleEndian;
|
|
|
|
|
|
|
|
|
|
|
|
fileHeader.SampleInteral = nSampleInterval;
|
|
|
|
|
|
fileHeader.SampleNumber = nSampleNumber;
|
|
|
|
|
|
fileHeader.headerBuffer = binaryHeader;
|
|
|
|
|
|
return fileHeader;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given a BinaryReader, reads the trace header.
|
|
|
|
|
|
/// Assumes that the trace header is the next item to be read.
|
|
|
|
|
|
/// Assumes that the byte order is Big Endian.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual ITraceHeader ReadTraceHeader(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ReadTraceHeader(reader, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Given a BinaryReader, reads the trace header.
|
|
|
|
|
|
/// Assumes that the trace header is the next item to be read.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual ITraceHeader ReadTraceHeader(BinaryReader reader, bool isLittleEndian)
|
|
|
|
|
|
{
|
|
|
|
|
|
var traceHeader = new TraceHeader();
|
|
|
|
|
|
var headerBytes = reader.ReadBytes(_traceHeaderSize);
|
|
|
|
|
|
if (headerBytes.Length < _traceHeaderSize)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
if (headerBytes.Length >= Options.TraceHeaderLocationForCrosslineNumber + 4)
|
|
|
|
|
|
traceHeader.CrosslineNumber = traceHeader.TraceNumber =
|
|
|
|
|
|
ToInt32(headerBytes, Options.TraceHeaderLocationForCrosslineNumber, isLittleEndian);
|
|
|
|
|
|
if (headerBytes.Length >= Options.TraceHeaderLocationForInlineNumber + 4)
|
|
|
|
|
|
traceHeader.InlineNumber = ToInt32(headerBytes, Options.TraceHeaderLocationForInlineNumber, isLittleEndian);
|
|
|
|
|
|
if (headerBytes.Length >= _sampleCountIndex + 3)
|
|
|
|
|
|
traceHeader.SampleCount = ToInt16(headerBytes, _sampleCountIndex, isLittleEndian);
|
|
|
|
|
|
|
|
|
|
|
|
traceHeader.Delay = ToInt16(headerBytes, Options.TraceHeaderLocationForDelay, isLittleEndian);
|
|
|
|
|
|
|
|
|
|
|
|
traceHeader.TraceNumber = ToInt32(headerBytes, Options.TraceHeaderLocationForTraceNumber, isLittleEndian);
|
|
|
|
|
|
|
|
|
|
|
|
traceHeader.X = ToInt32(headerBytes, Options.TraceHeaderLocationForSourceXLocation, isLittleEndian);
|
|
|
|
|
|
traceHeader.Y = ToInt32(headerBytes, Options.TraceHeaderLocationForSourceYLocation, isLittleEndian);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
traceHeader.headerBuffer = headerBytes;
|
|
|
|
|
|
// System.Diagnostics.Trace.WriteLine($"{traceHeader.X},{traceHeader.Y}");
|
|
|
|
|
|
// System.Diagnostics.Trace.WriteLine($"{ToInt32(headerBytes, 0, isLittleEndian)},{ToInt32(headerBytes, 4, isLittleEndian)},{ToInt32(headerBytes, 8, isLittleEndian)},{ToInt32(headerBytes, 12, isLittleEndian)},{ToInt32(headerBytes, 16, isLittleEndian)},{ToInt32(headerBytes, 20, isLittleEndian)},{ToInt32(headerBytes, 24, isLittleEndian)}");
|
|
|
|
|
|
return traceHeader;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Reads the trace (header and sample values).
|
|
|
|
|
|
/// Assumes that the trace header is the next item to be read.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual ITrace ReadTrace(BinaryReader reader, FormatCode sampleFormat, bool isLittleEndian)
|
|
|
|
|
|
{
|
|
|
|
|
|
var header = ReadTraceHeader(reader, isLittleEndian);
|
|
|
|
|
|
if (header == null)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
var values = ReadTrace(reader, sampleFormat, header.SampleCount, isLittleEndian);
|
|
|
|
|
|
return new Trace { Header = header, Values = values };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Assuming the trace header has been read, reads the array of sample values
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public virtual IList<float> ReadTrace(BinaryReader reader, FormatCode sampleFormat, int sampleCount, bool isLittleEndian)
|
|
|
|
|
|
{
|
|
|
|
|
|
var trace = new float[sampleCount];
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < sampleCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (sampleFormat)
|
|
|
|
|
|
{
|
|
|
|
|
|
case FormatCode.IbmFloatingPoint4:
|
|
|
|
|
|
trace[i] = reader.ReadSingleIbm();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case FormatCode.IeeeFloatingPoint4:
|
|
|
|
|
|
trace[i] = isLittleEndian ?
|
|
|
|
|
|
reader.ReadSingle() :
|
|
|
|
|
|
ReadReversedSingle(reader);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case FormatCode.TwosComplementInteger1:
|
|
|
|
|
|
trace[i] = ReadSignedByte(reader);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case FormatCode.TwosComplementInteger2:
|
|
|
|
|
|
trace[i] = isLittleEndian ?
|
|
|
|
|
|
reader.ReadInt16() :
|
|
|
|
|
|
reader.ReadInt16BigEndian();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case FormatCode.TwosComplementInteger4:
|
|
|
|
|
|
trace[i] = isLittleEndian ?
|
|
|
|
|
|
reader.ReadInt32() :
|
|
|
|
|
|
reader.ReadInt32BigEndian();
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new NotSupportedException(
|
|
|
|
|
|
String.Format("Unsupported sample format: {0}. Send an email to dev@segy.net to request support for this format.", sampleFormat));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (EndOfStreamException) { /* Encountered end of stream before end of trace. Leave remaining trace samples as zero */ }
|
|
|
|
|
|
return trace;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Behind the Scenes
|
|
|
|
|
|
|
|
|
|
|
|
public const int _binaryHeaderSize = 400;
|
|
|
|
|
|
public const int _traceHeaderSize = 240;
|
|
|
|
|
|
public const int _sampleFormatIndex = 24;
|
|
|
|
|
|
public const int _sampleCountIndex = 114;
|
|
|
|
|
|
|
|
|
|
|
|
private string InsertNewLines(string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rows = Options.TextHeaderRowCount;
|
|
|
|
|
|
var cols = Options.TextHeaderColumnCount;
|
|
|
|
|
|
var result = new StringBuilder(text.Length + rows);
|
|
|
|
|
|
for (int i = 0; i < 1 + text.Length / cols; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var line = new string(text.Skip(cols * i).Take(cols).ToArray());
|
|
|
|
|
|
result.AppendLine(line);
|
|
|
|
|
|
}
|
|
|
|
|
|
return result.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static int ToInt16(byte[] bytes, int index, bool isLittleEndian)
|
|
|
|
|
|
{
|
|
|
|
|
|
return isLittleEndian ?
|
|
|
|
|
|
BitConverter.ToInt16(bytes, index) :
|
|
|
|
|
|
IbmConverter.ToInt16(bytes, index);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static int ToInt32(byte[] bytes, int index, bool isLittleEndian)
|
|
|
|
|
|
{
|
|
|
|
|
|
return isLittleEndian ?
|
|
|
|
|
|
BitConverter.ToInt32(bytes, index) :
|
|
|
|
|
|
IbmConverter.ToInt32(bytes, index);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static float ReadSignedByte(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte b = reader.ReadByte();
|
|
|
|
|
|
return b < 128 ? b : b - 256;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static float ReadReversedSingle(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
var b = reader.ReadBytes(4).Reverse().ToArray();
|
|
|
|
|
|
return BitConverter.ToSingle(b, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|