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; } } /// /// Responsible for reading SEGY files given a path or a Stream. /// 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 /// /// Given a file path, reads entire SEGY file into memory /// public virtual ISegyFile Read(string path, IReadingProgress progress = null) { using (var stream = File.OpenRead(path)) return Read(stream, progress); } /// /// Given stream, reads entire SEGY file into memory. /// Assumes the stream is at the start of the file. /// public virtual ISegyFile Read(Stream stream, IReadingProgress progress = null) { return Read(stream, int.MaxValue, progress); } /// /// 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. /// 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(); 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; } } /// /// Given a BinaryReader, reads the SEGY File Header into memory. /// Asummes the BinaryReader is at the start of the file. /// public virtual IFileHeader ReadFileHeader(BinaryReader reader) { var text = ReadTextHeader(reader); FileHeader header = ReadBinaryHeader(reader) as FileHeader; header.Text = text; return header; } /// /// Given a file path reads the text header from the beginning /// of the SEGY file. /// public virtual string ReadTextHeader(string path) { using (var stream = File.OpenRead(path)) return ReadTextHeader(stream); } /// /// Given a stream reads the text header. /// Assumes the stream is at the start of the file. /// public virtual string ReadTextHeader(Stream stream) { using (var reader = new BinaryReader(stream)) return ReadTextHeader(reader); } /// /// Given a BinaryReader reads the text header. /// Assumes the BinaryReader is at the start of the file. /// 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 /// /// Given a BinaryReader, reads the binary header. /// Assumes that the binary header is the next item to be read. /// 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; } /// /// 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. /// public virtual ITraceHeader ReadTraceHeader(BinaryReader reader) { return ReadTraceHeader(reader, false); } /// /// Given a BinaryReader, reads the trace header. /// Assumes that the trace header is the next item to be read. /// 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; } /// /// Reads the trace (header and sample values). /// Assumes that the trace header is the next item to be read. /// 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 }; } /// /// Assuming the trace header has been read, reads the array of sample values /// public virtual IList 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 } }