You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
14 KiB
C#

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();
}
/// <summary>
/// 将字节数组转换为16位整数。
/// </summary>
/// <param name="bytes">包含要转换的字节的字节数组。</param>
/// <param name="index">要转换的字节在字节数组中的起始索引。</param>
/// <param name="isLittleEndian">指示字节顺序是否为小端序。</param>
/// <returns>转换后的16位整数。</returns>
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
}
}