一、引言
在地震勘探领域,SEGY(Society of Exploration Geophysicists Y-data)文件格式是常见的地震数据存储格式。对于地震数据的可视化,通常会将 SEGY 文件中的振幅数据通过图像进行展示,以便进行分析。本文将介绍如何使用 C# WPF 应用程序绘制基于 SEGY 数据的二维地震图。
二、需求
我们将从 SEGY 文件中读取地震数据,提取振幅信息,并通过色卡将这些振幅映射到颜色空间中。最终,我们将在 WPF 应用中生成二维地震图,绘制出不同时间步长和采样点的地震波形。
2.1 Segy文件格式简介
EGY 是一种用于存储地震数据的标准文件格式,包含头部信息和多个地震数据轨迹。每个轨迹包含一系列时间采样点的振幅值。绘制地震图时,我们需要读取这些轨迹,并将其显示为图像。
2.2准备工作
首先,我们需要安装和配置 SEGY 文件的读取工具。为了简化这个过程,我们假设你已经有了一个 SegyReader
类,它能从 SEGY 文件中读取轨迹数据。
三、WPF绘制二维地震图
在本项目中,我们将使用 WPF 和 WriteableBitmap
来绘制二维地震图。WriteableBitmap
允许我们直接操作像素数据,因此非常适合此类任务。
3.1读取SEGY文件并提取数据
我们使用一个 SEGY 文件读取器 (SegyReader
) 来读取 SEGY 文件中的数据。以下是读取文件头和轨迹数据的代码:
var reader = new SegyReader(); var filePath = @"D:\Code\python\Study\Segy\Inline790.segy"; var header = reader.ReadTextHeader(filePath); // 读取文本头部 ISegyFile line = reader.Read(filePath); // 读取地震数据 ITrace trace = line.Traces[0];int traceCount = line.Traces.Count; // 控制绘图宽度,防止越界 int sampleCount = line.Traces[0].Values.Count; // 控制绘图高度,防止越界
解析Segy数据文件,参考来源:https://github.com/jfoshee/UnpluggedSegy
3.2色卡映射
为了将振幅值转换为颜色,我们使用一个色卡。色卡是一个预定义的图像文件,其中每个像素代表一种振幅值对应的颜色。通过将振幅值归一化到色卡的高度范围内,我们可以将振幅映射到色卡上的颜色。
// 色卡 Bitmap colorRamp = Properties.Resources.ColorRamp; int colorRampHeight = colorRamp.Height; // 1023 int colorRampWidth = colorRamp.Width; // 45
3.3计算振幅范围
我们需要遍历所有的轨迹,计算振幅的最小值和最大值。这将帮助我们将振幅数据归一化到 0 到 1 的范围。
// 获取振幅的最小值和最大值,以便后续映射 float minAmplitude = float.MaxValue; float maxAmplitude = float.MinValue;foreach (var itemTrace in line.Traces) {foreach (var amplitude in itemTrace.Values){if (amplitude < minAmplitude) minAmplitude = amplitude;if (amplitude > maxAmplitude) maxAmplitude = amplitude;} }
3.4创建图像并绘制数据
接下来,我们将使用 WriteableBitmap
来创建图像并绘制每个像素的颜色。对于每个采样点,我们使用色卡来获取对应的颜色,并将其写入图像数据。
WriteableBitmap bitmap = new WriteableBitmap(traceCount, sampleCount, 192, 192, PixelFormats.Bgra32, null); byte[] pixelData = new byte[traceCount * sampleCount * 4]; // BGRA 格式for (int t = 0; t < traceCount; t++) {for (int s = 0; s < sampleCount; s++){float amplitude = line.Traces[t].Values[s];// 归一化振幅到 [0, 1] 范围double normalizedAmplitude = (amplitude - minAmplitude) / (maxAmplitude - minAmplitude);// 映射到色卡的高度int colorIndex = colorRampHeight - 1 - (int)(normalizedAmplitude * (colorRampHeight - 1));colorIndex = Clamp(colorIndex, 0, colorRampHeight - 1); // 防止越界// 获取对应的颜色 System.Drawing.Color color;lock (colorRamp){color = colorRamp.GetPixel(colorRampWidth / 2, colorIndex); // 使用色卡的中间列 }// 写入像素数组 (BGRA 格式)int pixelIndex = (s * traceCount + t) * 4;pixelData[pixelIndex + 0] = color.B; // 蓝色pixelData[pixelIndex + 1] = color.G; // 绿色pixelData[pixelIndex + 2] = color.R; // 红色pixelData[pixelIndex + 3] = 255; // 不透明度 } }// 写入图像数据 bitmap.WritePixels(new Int32Rect(0, 0, traceCount, sampleCount), pixelData, traceCount * 4, 0); SeismicImage.Source = bitmap; // 将图像显示在界面
3.5防止越界
在获取色卡颜色时,我们使用了 Clamp
函数来防止数组越界。这确保了我们不会超出色卡的高度范围。
private void DrawSeismicData() {var reader = new SegyReader();var filePath = @"D:\Code\python\Study\Segy\Inline790.segy";var header = reader.ReadTextHeader(filePath);ISegyFile line = reader.Read(filePath);ITrace trace = line.Traces[0];int traceCount = line.Traces.Count; // 控制绘图宽度,防止越界int sampleCount = line.Traces[0].Values.Count; // 控制绘图高度,防止越界//色卡System.Drawing.Bitmap colorRamp = Properties.Resources.ColorRamp;int colorRampHeight = colorRamp.Height; // 1023int colorRampWidth = colorRamp.Width; // 45 WriteableBitmap bitmap = new WriteableBitmap(traceCount, sampleCount, 192, 192, PixelFormats.Bgra32, null);// 创建一个像素数组,BGRA 格式byte[] pixelData = new byte[traceCount * sampleCount * 4];// 获取振幅的最小值和最大值,以便后续映射float minAmplitude = float.MaxValue;float maxAmplitude = float.MinValue;foreach (var itemTrace in line.Traces){foreach (var amplitude in itemTrace.Values){if (amplitude < minAmplitude) minAmplitude = amplitude;if (amplitude > maxAmplitude) maxAmplitude = amplitude;}}for (int t = 0; t < traceCount; t++){for (int s = 0; s < sampleCount; s++){float amplitude = line.Traces[t].Values[s];// 1. 使用原始振幅值直接映射到色卡的高度// 例如,假设振幅的范围为 [minAmplitude, maxAmplitude],则将其映射到色卡的高度double normalizedAmplitude = (amplitude - minAmplitude) / (maxAmplitude - minAmplitude); // 归一化振幅到 [0, 1] 范围// 2. 将归一化的振幅映射到色卡的高度int colorIndex = colorRampHeight - 1 - (int)(normalizedAmplitude * (colorRampHeight - 1));colorIndex = Clamp(colorIndex, 0, colorRampHeight - 1); // 防止越界// 3. 从色卡图像中获取对应的颜色 System.Drawing.Color color;lock (colorRamp){color = colorRamp.GetPixel(colorRampWidth / 2, colorIndex); // 使用色卡的中间列 }// 4. 将颜色写入像素数组 (BGRA 格式) int pixelIndex = (s * traceCount + t) * 4;pixelData[pixelIndex + 0] = color.B; // 蓝色pixelData[pixelIndex + 1] = color.G; // 绿色pixelData[pixelIndex + 2] = color.R; // 红色pixelData[pixelIndex + 3] = 255; // 不透明度 }}// 写入图像数据bitmap.WritePixels(new Int32Rect(0, 0, traceCount, sampleCount), pixelData, traceCount * 4, 0);SeismicImage.Source = bitmap;this.ColorImage.Source= ConvertToImageSource(colorRamp); }public static int Clamp(int value, int min, int max) {if (value < min) return min;if (value > max) return max;return value; }/// <summary> /// System.Drawing.Bitmap 转换为 ImageSource /// </summary> /// <param name="bitmap"></param> /// <returns></returns> public static BitmapImage ConvertToImageSource(System.Drawing.Image image) {if (image == null) return null;using (var memoryStream = new MemoryStream()){image.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png); // 保存为 PNG 格式memoryStream.Position = 0;var bitmapImage = new BitmapImage();bitmapImage.BeginInit();bitmapImage.CacheOption = BitmapCacheOption.OnLoad;bitmapImage.StreamSource = memoryStream;bitmapImage.EndInit();bitmapImage.Freeze(); // 冻结以便跨线程使用return bitmapImage;} }
五、效果图
六、总结
本文演示了如何使用 C# WPF 绘制二维地震图,结合 SEGY 文件格式和色卡映射。通过读取 SEGY 文件的轨迹数据并对振幅进行归一化,我们可以将其映射到色卡的颜色值,并将其呈现在 WPF 图像控件中。这种方法不仅能有效地展示地震数据的振幅变化,也为地震勘探数据的可视化分析提供了强有力的支持。