SEGY is a widely used format for storing seismic trace data, defined by the Society of Exploration Geophysicists publication "Digital Tape Standards" (http://www.seg.org). Using the SEGY reader class SoVRSegyFileReader( C++ | Java | .NET ), VolumeViz can load a 3D volume directly from a SEGY format file. For example, we can directly load the file Waha8.sgy provided with the Open Inventor SDK. This can be useful for very small volumes or to extract information from the file and trace headers in the file. It is not recommended to directly load large seismic data sets for visualization because they are normally much larger than the available system and GPU memory. Generally seismic data sets should be converted to LDM (Large Data Management) format as described in section 22.2.5 “Convert to LDM”. Note that there is an LDM converter example specifically for SEGY files because it is often necessary to give the volume reader more information in order to correctly interpret the file. Also it is often necessary to store additional information in the LDM file header, for example the ranges of line and cross-line numbers.
The data will be mapped to the axes of the volume as follows:
Traces are mapped onto the volume X axis (increasing X = increasing time).
Lines are mapped onto the volume Y axis (increasing Y = increasing trace number).
Cross-lines are mapped onto the volume Z axis (increasing Z = increasing line number).
The SEGY reader provides some convenience methods for retrieving essential information about a survey. For example the range (min and max values) and step size for inlines, cross-lines and time. The reader also provides a general mechanism for querying the SEGY file header and trace headers, which we will explain in the following sub-section.
First we’ll look at the range (min and max values) and the step size for inlines, crosslines and time. Given the correct range and step size we can compute the actual number of inlines, crosslines and samples. Start by telling VolumeViz what file to open and getting a reference to the SEGY reader. This process is the same for any file format and reader, as explained in the previous sub-sections. One way is to create the SoVolumeData( C++ | Java | .NET ) node, set the file name, then query the reader from the SoVolumeData( C++ | Java | .NET ). A second way is to create an instance of SoVRSegyFileReader( C++ | Java | .NET ) and set the file name. In either case we now have an SoVRSegyFileReader( C++ | Java | .NET ) that we can use like the following code.
SoVRSegyFileReader* pReader = (SoVRSegyFileReader*)pVolData->getReader(); // Get range and step size int ilFrom, ilTo, ilStep; int clFrom, clTo, clStep; int zFrom, zTo, zStep; pReader->getInlineRange ( ilFrom, ilTo, ilStep ); pReader->getCrosslineRange( clFrom, clTo, clStep ); pReader->getZRange ( zFrom, zTo, zStep ); // Compute number of Lines/Crosslines int numInlines = ilTo - ilFrom + 1; if (ilStep > 1) numInlines = (int)ceil((float)numInlines / ilStep); int numCrosslines = clTo - clFrom + 1; if (clStep > 1) numCrosslines = (int)ceil((float)numCrosslines / clStep);
SoVRSegyFileReader Reader = (SoVRSegyFileReader)VolData.Reader; // Get range and step size int ilFrom, ilTo, ilStep; int clFrom, clTo, clStep; int zFrom, zTo, zStep; Reader.GetInlineRange(out ilFrom, out ilTo, out ilStep); Reader.GetCrosslineRange(out clFrom, out clTo, out clStep); Reader.GetZRange(out zFrom, out zTo, out zStep); // Compute number of Lines/Crosslines int numInlines = ilTo - ilFrom + 1; if (ilStep > 1) numInlines = (int)Math.Ceiling((float)numInlines / ilStep); int numCrossLines = clTo - clFrom + 1; if (clStep > 1) numCrossLines = (int)Math.Ceiling((float)numCrossLines / clStep);
SoVRSegyFileReader reader = (SoVRSegyFileReader)volData.getReader(); // Get range and step size // Each query returns start, end and step size int[] inlineRange = reader.getInlineRange(); int[] crlineRange = reader.getCrosslineRange(); int[] zRange = reader.getZRange(); // Compute number of Lines/Crosslines int numInlines = inlineRange[1] - inlineRange[0] + 1; if (inlineRange[2] > 1) // Inline step numInlines = (int)Math.ceil((float)numInlines / inlineRange[2]); int numCrossLines = crlineRange[1] - crlineRange[0] + 1; if (crlineRange[2] > 1) numCrossLines = (int)Math.ceil((float)numCrossLines / crlineRange[2]);
For the Waha8.sgy data set, we know from the file’s text header that the inline range should be 720 to 978 by steps of 3. In fact by default we get 1 to 87 by steps of 1. The problem is that the SEGY reader expects to find the inline number at byte 5 (tracr) by default. For Waha8.sgy, we know from the file’s text header that it’s actually stored at byte 9 (fldr). In the next sub-section we will see how to adjust byte positions when loading a SEGY file.
The getDataChar (get data characteristics) method returns the characteristics of the 3D data volume that the reader will extract from the SEGY file. volSize is the geometric extent of the volume in 3D space, in other words the bounding box. By default this is an arbitrary bounding box computed from the voxel dimensions of the volume. We will discuss in other sections how to use the actual survey coordinates and how to scale the time axis to give a more useful display. dataType indicates the type of data values in the volume. For Waha8.sgy, the data type should be SIGNED_BYTE, corresponding to SEGY format 8. volDim is the dimensions of the volume in voxels. For Waha8.sgy, the volume dimensions should be 876 x 67 x 87. Remember that the SEGY reader loads traces into contiguous memory, so this is samples x crosslines x lines.
The getP1P2P3P4Coordinates method returns the coordinates of the corners of the survey as double precision 2D vectors. It also returns a boolean value indicating if the survey should be interpreted in a left-handed or right-handed coordinate system (Note: not in the Java API). In this case we expect P2 to be approximately 1130370,604625 (based on the textual header). But by default the X value of P2 is returned as 1233779735. The problem, as mentioned earlier, is that the SEGY reader expects all trace header values to be integers (as stated in the spec) and these values are actually stored as floating point. In the next section we will see how to adjust the data format.
// Get the coordinates of the corners of the survey SbVec2d p1, p2, p3, p4; SbBool rightHanded = pReader->getP1P2P3Coordinates( p1, p2, p3, p4 );
// Get the coordinates of the corners of the survey SbVec2d p1, p2, p3, p4; bool rightHanded = reader.getP1P2P3Coordinates( out p1, out p2, out p3, out p4 );
// Get the coordinates of the corners of the survey SbVec2d[] p1p2p3p4 = reader.getP1P2P3Coordinates();
Finally, we might want to check what byte positions the reader actually used to determine the inline and crossline numbers. The getSegyTraceHeaderBytePosition method returns an SoVRSegyTraceHeaderBytePosition( C++ | Java | .NET ) object which contains the byte positions of all the trace header fields. Use the getBytePosition method to get the position of a specific header field. As expected, we see that by default the reader is expecting the inline number at byte 5 and the crossline number at byte 21.
// Get the actual byte positions the reader used SoVRSegyTraceHeaderBytePosition bytePos = pReader->getSegyTraceHeaderBytePosition(); int ilineByte = bytePos.getBytePosition( SoVRSegyTraceHeaderBytePosition::SEGY_INLINE ); int xlineByte = bytePos.getBytePosition( SoVRSegyTraceHeaderBytePosition::SEGY_CROSSLINE );
// Get the actual byte positions the reader used SoVRSegyTraceHeaderBytePosition bytePos = Reader.GetSegyTraceHeaderBytePosition(); byte ilineByte = bytePos.GetBytePosition( SoVRSegyTraceHeaderBytePosition.TraceAttributes.SEGY_INLINE ); byte xlineByte = bytePos.GetBytePosition( SoVRSegyTraceHeaderBytePosition.TraceAttributes.SEGY_CROSSLINE );
// Get the actual byte positions the reader used SoVRSegyTraceHeaderBytePosition bytePos = reader.getSegyTraceHeaderBytePosition(); byte ilineByte = bytePos.getBytePosition( SoVRSegyTraceHeaderBytePosition.TraceAttributes.SEGY_INLINE ); byte xlineByte = bytePos.getBytePosition( SoVRSegyTraceHeaderBytePosition.TraceAttributes.SEGY_CROSSLINE );
In many cases the SEGY reader can automatically determine the characteristics of a SEGY file and correctly load the 3D volume. The reader uses a simple detection algorithm by default to maximize performance, but more complex algorithms may be needed to correctly interpret the file. For example when there are a variable number of traces per line and/or a variable number of samples per trace. Some SoPreferences( C++ | Java | .NET ) parameters are provided to tell the reader to use the more complex (but time consuming) algorithms. In other cases the file may follow proprietary conventions that put critical values in non-standard locations in the trace header and/or store float values in integer trace header fields. In these cases the SoVRSegyTraceHeaderBytePosition( C++ | Java | .NET ) class allows you to specify the actual locations of trace attributes and/or the actual data type of those parameters. However it may be necessary to visually inspect the textual portion of the SEGY file header and/or have prior knowledge of the conventions used to write the file.
Here are some of the problems commonly encountered with SEGY files and a brief description of the solution:
Line and Crossline numbers stored at non-standard locations in the trace header. Use the SoVRSegyTraceHeaderBytePosition( C++ | Java | .NET ) method setBytePosition to specify the actual locations.
Integer fields, e.g. survey coordinates, actually contain floating point values. Use the SoVRSegyTraceHeaderBytePosition( C++ | Java | .NET ) method setByteFormat to specify the actual format.
File header says data is format 1 (IBM float), but data is actually IEEE float format. Use the SoPreferences( C++ | Java | .NET ) method setBool to set IVVR_SEGY_FLOATISIEEE.
Reader does not correctly detect byte ordering (little-endian vs big-endian). Use the SoPreferences( C++ | Java | .NET ) method setBool to set IVVR_SEGY_SWAPBYTES.
Reader does not correctly detect variable length traces. Use the SoPreferences( C++ | Java | .NET ) method setBool to set IVVR_SEGY_INCONSTANT_TRACE_LENGTH.
Reader does not correctly detect variable number of traces per line. Use the SoPreferences( C++ | Java | .NET ) method setBool to set IVVR_SEGY_IRREGULAR_TRACE_NUM.
If we are unable to read a SEGY file (or the resulting volume is incorrect), the next step could be to investigate the contents of the file. Specifically we want to look at the textual file header, the binary file header and the trace headers. If we’re really not sure what convention was used to write the file, then we may need to look for a pattern in the primary values of the trace headers. There are tools available to help with this, but it may be convenient to use the Open Inventor SDK and walking through this will illustrate some of the powerful features of the SEGY reader.
It may be useful to enable debug output from the SEGY reader. Use the SoPreferences( C++ | Java | .NET ) method setBool to set IVVR_SEGY_DEBUG. The resulting output may give some clues about how the reader attempted to interpret the file. FEI Customer Support will normally ask for this output if a problem is reported reading a SEGY file. Note that on Windows the output will only appear on screen if the application was built as a “console” application or explicitly creates a console window. If the application is run in the Visual Studio debugger, the output will appear in the Visual Studio output pane. The application can also override the Open Inventor error handler and handle the strings as desired.
The reader method getSegyTextHeader returns the text header as a string object. The text header should contain 40 lines of 80 characters. It is actually a single block of 3200 characters because there are no “newline” characters. By default the reader assumes the text header is in the traditional EBCDIC format. Call the method setSegyTextHeaderAscii to override this behavior. Also note that the reader supports the SEGY “Extended Textual File Header” feature, which allows the file to contain additional blocks of 3200 characters. The getSegyTextHeader method returns a single string object that contains the concatenation of all the text blocks. You can detect this case by querying the length of the string.
// Get/print SEGY text file header // Should be 40 lines of 80 characters (as a block of chars) SbString textHeader = pReader->getSegyTextHeader(); if (!textHeader.isEmpty()) { printf( "SEGY Text header:\n" ); for (int iline = 0; iline < 40; ++iline) { int start = iline * 80; SbString substr = textHeader.getSubString( start, start + 79 ); printf( "%s", substr.getString() ); } }
// Get/print SEGY text file header // Should be 40 lines of 80 characters (as a block of chars) string textHeader = Reader.GetSegyTextHeader(); if (textHeader.Length > 0) { System.Console.Write("SEGY Text header:\n"); for (int iline = 0; iline < 40; ++iline) { int start = iline * 80; string substr = textHeader.Substring(start, 80); System.Console.Write(substr); } }
// Get/print text file header // Should be 40 lines of 80 characters (as a block of chars) String textHeader = reader.getSegyTextHeader(); if (! textHeader.isEmpty()) { System.out.printf("SEGY Text header:\n"); for (int iline = 0; iline < 40; ++iline) { int start = iline * 80; String substr = textHeader.substring(start, start+79); System.out.printf("%s\n", substr); } }
The reader method getSegyFileHeader returns the binary header in an instance of the class SoVRSegyFileHeader( C++ | Java | .NET ). This class allows the binary file header fields to be conveniently accessed as member variables. For example we will probably want to know the sample interval, the number of samples per trace and the sample data type (format code).
// Get the binary file header int sampleInterval; int samplesPerTrace; int sampleFormat; SoVRSegyFileHeader fileHeader; SbBool ok = reader.getSegyFileHeader( fileHeader ); if (ok) { sampleInterval = fileHeader.hdt; // Byte 17 samplesPerTrace = fileHeader.hns; // Byte 21 sampleFormat = fileHeader.format; // Byte 25 }
// Get the binary file header int sampleInterval; int samplesPerTrace; int sampleFormat; SoVRSegyFileHeader fileHeader; bool ok = Reader.GetSegyFileHeader(out fileHeader); if (ok) { sampleInterval = fileHeader.Hdt; // Byte 17 samplesPerTrace = fileHeader.Hns; // Byte 21 sampleFormat = fileHeader.Format; // Byte 25 }
// Get the binary file header int sampleInterval; int samplesPerTrace; int sampleFormat; SoVRSegyFileHeader fileHeader = reader.getSegyFileHeader(); if (fileHeader != null) { . . . }
For our test data, Waha8.sgy, these values are:
Sample interval | = 4000 micro-seconds, |
Samples per trace | = 876 samples and |
Sample data type | = format 8 (signed byte). |
Now let’s look at the trace headers. The getNumTraces method returns the total number of traces in the file. The content of a trace header is returned by the getSegyTraceHeader method. With these two methods, we could, for example, step through all the trace headers and determine the range (min and max values) of each trace header field (or at least the interesting ones). It may be useful to print the values of potentially useful fields for the first N trace headers. If we write these values into a file in CSV (Comma Separated Value) format, we can then load the file into a spreadsheet program like Excel for convenient viewing. Usually by knowing the expected range of inline and crossline numbers, or simply by seeing the pattern of repetition, we can determine which fields contain the inline and crossline numbers. The “TraceHeaders” example program fully implements this, similar to the following code.
// Get total number of traces
int numTraces = pReader->getNumTraces();
// Get trace header info
SoVRSegyTraceIdHeader trHeader;
for (int i = 0; i < numTraces; ++i) {
ok = pReader->getSegyTraceHeader( i, trHeader );
int tracl = trHeader.tracl;
int tracr = trHeader.tracr;
int fldr = trHeader.fldr;
}
// Get total number of traces
int numTraces = Reader.GetNumTraces();
// Get trace header info
SoVRSegyTraceIdHeader trHeader;
for (int i = 0; i < numTraces; ++i)
{
ok = Reader.GetSegyTraceHeader(i, out trHeader);
int tracl = trHeader.Tracl;
int tracr = trHeader.Tracr;
int fldr = trHeader.Fldr;
}
// Get total number of traces
int numTraces = reader.getNumTraces();
// Get trace header info
SoVRSegyTraceIdHeader trHeader;
for (int i = 0; i < numTraces; ++i)
{
trHeader = reader.getSegyTraceHeader(i);
. . .
}