AGG第二十三课 ctrl目录下agg_slider_ctrl基本使用

本文介绍如何在AGG库中使用滚动条控件agg::slider_ctrl,包括控件的初始化、添加到事件响应列表及渲染过程。通过实例演示如何结合滚动条动态调整图形的尺寸。

引言:

    在agg/examples目录下,提供的很多例子都携带滚动条的,并且通过滚动条,可以动态看到图像色彩的渐变过程。因此一下agg::slider_ctrl工具类。

图形:

  /*

 

                                           *(x2,y2)

                                      *     *

                              *             *

                          *                 *

                    *                       *

                *                           *

            *                               * 

      *                                     *

(x1,y1)**************************************(x2,y1)

  */

函数:

构造函数:slider_ctrl(double x1, double y1, doublex2, double y2, bool flip_y=false)

   m_slider1.range(1, 100);//设置滚动条的取值范围

   m_slider1.num_steps(10);//设置滚动条的步宽,

   m_slider1.value(1.0);//设置初始值

   m_slider1.label("Pixel size=%1.0f");//显示当前滚动条的值

 m_slider1.no_transform();//不允许缩放

 int nScaleX =m_slider1.value();//获取当前值



1)添加头文件

#include "agg/include/ctrl/agg_slider_ctrl.h"

2)定义:

agg::slider_ctrl<agg::rgba8>   m_slider1;

3)添加到事件响应列表

添加到windows platform例子中:

 add_ctrl(m_slider1);

/*

通过上面的函数,将控件元素添加到事件监听列表中,当鼠标或者键盘发生事件的时候

,会传递给该控件,由该控件判断是否是属于自己的事件(是否正在滑动滚动条)。

通过注释该函数,作者可体会到,滑动进度条,没有任何的反映。

*/

4)渲染控件

agg::render_ctrl(ras, sl, renb, m_slider1);

5)由于是把代码放在on_draw,需要强行刷新

force_redraw();


简单的应用例子如下:

#include "agg/include/agg_basics.h"

#include "agg/include/agg_conv_curve.h"

#include "agg/include/agg_trans_perspective.h"

#include "agg/include/agg_renderer_scanline.h"

#include "agg/include/agg_rendering_buffer.h"

#include "agg/include/agg_rasterizer_scanline_aa.h"

#include "agg/include/agg_scanline_u.h"

#include "agg/include/agg_pixfmt_rgb.h"

#include "agg/include/platform/agg_platform_support.h"

#include "agg/include/agg_ellipse.h"

#include "agg/include/agg_conv_contour.h"

#include "agg/include/agg_conv_stroke.h"

#include "agg/include/agg_renderer_base.h"

#include "agg/include/agg_path_storage.h"

#include "agg/include/ctrl/agg_slider_ctrl.h"


class the_application:public agg::platform_support

{

public:


  the_application(agg::pix_format_e format, bool flip_y):agg::platform_support(format,flip_y),pix_fmt(rbuf_window()),ren_bas(pix_fmt),

    m_slider1(80, 250,    600-10, 276,    !flip_y),

  {

    add_ctrl(m_slider1);


    m_slider1.range(1.0, 100.0);

    m_slider1.num_steps(10);

    m_slider1.value(1.0);

    m_slider1.label("Pixel size=%1.0f");

    m_slider1.no_transform();

 

  }



  void draw_ellipse()

  {

    //Rendering Buffer渲染的内存块,就是即将显示界面的颜色内存块,其中agg::rendering_buffer

    //类提供了一系列直接操作某一个坐标点颜色的函数,例如row_ptr();

    agg::rendering_buffer &rbuf = rbuf_window();

    agg::pixfmt_bgr24 pixf(rbuf);

    agg::rgba8 color(255,0,0);

    agg::rgba c(344,1);



    //Renderers渲染器,我们可以看成是油漆,颜料,

    typedef agg::renderer_base<agg::pixfmt_bgr24> renderer_base_type;

    renderer_base_type renb(pixf);



    //Scanline Rasterizer光栅化,说她是画册,描述了所有我们即将描绘图案,颜色,线条,

    //但是并不切确,因为她只是一个容器,一个记载线段,标志,详细参数的规格说明书,

    //连草图都不是,她更像是一张菜谱,里面记录了食材,记录了烹饪的过程,仅此而已。

    agg::rasterizer_scanline_aa<> ras;

    agg::scanline_u8 sl;

    ren_bas.clear(agg::rgba8(255,255,255));

    



    int nScaleX = 0;

    int nScaleY = 0;

    agg::path_storage ps;

    ps.move_to(300,300);

    ps.line_to(320,300);

    ps.line_to(320,320);

    ps.line_to(300,320);

    ps.line_to(300,300);


    nScaleX = int(m_slider1.value())/10;

    nScaleY = int(m_slider2.value())/10; 

    agg::trans_affine mtx;

    mtx.scale(nScaleX,nScaleY); //x轴缩小到原来的一半

   // mtx.rotate(agg::deg2rad(40));//旋转30度

    mtx.translate(-320*(nScaleX-1) ,-320*(nScaleY-1));//X,Y坐标分别平移100

    typedef agg::conv_transform<agg::path_storage> ell_ct_type;

    ell_ct_type ctell(ps,mtx); //矩阵变换


    typedef agg::conv_stroke<ell_ct_type> ell_cc_cs_type;

    ell_cc_cs_type csccell(ctell);

    ras.add_path(csccell);

    agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba8(255,0,0));

    ras.reset();


    // Render the controls

    agg::render_ctrl(ras, sl, renb, m_slider1);

  

    return;

    

    

      

  }


  virtual void on_draw()

  {

    draw_ellipse();

  }


  virtual void on_mouse_button_down(int x, int y, unsigned flags)

  {

    if (flags == agg::mouse_left)

    {

      char str[50];

      sprintf(str,"Mouse location:(%d,%d)", x, y);

      message(str);

    }

  }


private:


  agg::slider_ctrl<agg::rgba8> m_slider1;

  agg::pixfmt_rgb24 pix_fmt;

  agg::renderer_base<agg::pixfmt_rgb24> ren_bas;

};




int agg_main(int argc, char* argv[])

{

  the_application app(agg::pix_format_bgr24,false);

  app.caption("AGG Example.Anti_Aliasing Demo");


  if (app.init(1800, 1800, agg::window_resize))

  {

    return app.run();

  }


  return -1;

}





     本文转自fengyuzaitu 51CTO博客,原文链接:http://blog.51cto.com/fengyuzaitu/1962143,如需转载请自行联系原作者

能帮我优化一下下面这段代码并增加一些注释吗import matplotlib matplotlib.use('Qt5Agg') from numpy import pi, sin import numpy as np import matplotlib.pyplot as plt from matplotlib.widgets import Slider, Button, RadioButtons def signal(amp, freq): return amp * sin(2 * pi * freq * t) axis_color = 'lightgoldenrodyellow' fig = plt.figure() ax = fig.add_subplot(111) fig.subplots_adjust(left=0.25, bottom=0.25) t = np.arange(-10, 10.0, 0.001) [line] = ax.plot(t, signal(5, 2), linewidth=2, color='red') ax.set_xlim([0, 1]) ax.set_ylim([-10, 10]) zoom_slider_ax = fig.add_axes([0.25, 0.1, 0.65, 0.03], facecolor=axis_color) zoom_slider = Slider(zoom_slider_ax, 'Zoom', -1, 1, valinit=0) def sliders_on_changed(val, scale_factor=0.25): cur_xlim = ax.get_xlim() cur_ylim = ax.get_ylim() scale = zoom_slider.val*scale_factor x_left = 0 + scale x_right = 1 - scale y_top = 10 - scale*10 y_bottom = -10 + scale*10 ax.set_xlim([x_left, x_right]) ax.set_ylim([y_bottom, y_top]) fig.canvas.draw_idle() zoom_slider.on_changed(sliders_on_changed) reset_button_ax = fig.add_axes([0.8, 0.025, 0.1, 0.04]) reset_button = Button(reset_button_ax, 'Reset', color=axis_color, hovercolor='0.975') def reset_button_on_clicked(mouse_event): zoom_slider.reset() reset_button.on_clicked(reset_button_on_clicked) color_radios_ax = fig.add_axes([0.025, 0.5, 0.15, 0.15], facecolor=axis_color) color_radios = RadioButtons(color_radios_ax, ('red', 'blue', 'green'), active=0) def color_radios_on_clicked(label): line.set_color(label) fig.canvas.draw_idle() color_radios.on_clicked(color_radios_on_clicked) plt.show()
05-24
import os import sys import numpy as np import pydicom import dicom_numpy import vtk from vtk.util import numpy_support from PyQt5.QtWidgets import ( QApplication, QMainWindow, QFileDialog, QVBoxLayout, QHBoxLayout, QWidget, QSlider, QLabel, QPushButton, QMessageBox, QProgressDialog ) from PyQt5.QtCore import Qt, QThread, pyqtSignal import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor def fix_qt_plugin_path(): """解决 Qt 平台插件无法初始化的问题""" try: from PyQt5.QtCore import QLibraryInfo plugin_path = QLibraryInfo.location(QLibraryInfo.PluginsPath) if os.path.exists(plugin_path): os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path return except ImportError: pass paths_to_try = [ os.path.join(sys.prefix, 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'), os.path.join(sys.prefix, 'Library', 'plugins'), os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Qt', 'plugins') ] for path in paths_to_try: if os.path.exists(path): os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = path break fix_qt_plugin_path() class DICOMLoader(QThread): progress_updated = pyqtSignal(int) loading_complete = pyqtSignal(object, object, object, object) # volume_array, spacing, origin, vtk_image loading_failed = pyqtSignal(str) def __init__(self, directory): super().__init__() self.directory = directory def run(self): try: # 获取所有DICOM文件 dicom_files = self.get_all_dicom_files(self.directory) if not dicom_files: self.loading_failed.emit("未找到DICOM文件") return # 读取并分组DICOM文件 series_dict = self.read_and_group_dicom_files(dicom_files) if not series_dict: self.loading_failed.emit("没有有效的DICOM图像") return # 选择第一个系列进行处理 series_uid = next(iter(series_dict)) datasets = series_dict[series_uid] # 处理DICOM数据集 volume_array, spacing, origin = self.process_dicom_datasets(datasets) # 转换为VTK图像 vtk_image = self.numpy_to_vtk(volume_array, spacing, origin) self.loading_complete.emit(volume_array, spacing, origin, vtk_image) except Exception as e: self.loading_failed.emit(f"加载DICOM文件失败: {str(e)}") def get_all_dicom_files(self, directory): """获取目录下所有DICOM文件""" dicom_files = [] for root, _, files in os.walk(directory): for file in files: if file.lower().endswith(('.dcm', '.dicm', '.dicom')): dicom_files.append(os.path.join(root, file)) return dicom_files def read_and_group_dicom_files(self, file_paths): """读取并分组DICOM文件""" series_dict = {} for i, file_path in enumerate(file_paths): try: ds = pydicom.dcmread(file_path) # 检查是否包含像素数据 if not hasattr(ds, 'pixel_array'): continue # 检查必要的定位信息 required_tags = ['ImagePositionPatient', 'ImageOrientationPatient', 'PixelSpacing'] if not all(hasattr(ds, tag) for tag in required_tags): continue # 按系列实例UID分组 series_uid = ds.SeriesInstanceUID if series_uid not in series_dict: series_dict[series_uid] = [] series_dict[series_uid].append(ds) # 更新进度 self.progress_updated.emit(int((i + 1) / len(file_paths) * 100)) except Exception as e: print(f"无法读取文件 {file_path}: {str(e)}") continue # 对每个系列按切片位置排序 for series_uid in series_dict: try: series_dict[series_uid].sort(key=lambda ds: float(ds.ImagePositionPatient[2])) except: pass # 如果排序失败,保持原顺序 return series_dict def process_dicom_datasets(self, datasets): """处理DICOM数据集并返回体积数据""" try: # 使用dicom-numpy组合体积数据 volume_array, ijk_to_xyz = dicom_numpy.combine_slices(datasets) # 获取间距和原点 spacing = np.array([ np.linalg.norm(ijk_to_xyz[:3, 0]), # X spacing np.linalg.norm(ijk_to_xyz[:3, 1]), # Y spacing np.linalg.norm(ijk_to_xyz[:3, 2]) # Z spacing ]) origin = ijk_to_xyz[:3, 3] # 调整数组方向以匹配VTK坐标系 volume_array = np.transpose(volume_array, (2, 1, 0)) return volume_array, spacing, origin except dicom_numpy.DicomImportException as e: raise Exception(f"DICOM导入错误: {str(e)}") except Exception as e: raise Exception(f"处理DICOM数据时出错: {str(e)}") def numpy_to_vtk(self, volume_array, spacing, origin): """将numpy数组转换为VTK图像""" # 确保数组是连续的 volume_array = np.ascontiguousarray(volume_array) # 根据数据类型选择合适的VTK类型 if volume_array.dtype == np.uint8: vtk_type = vtk.VTK_UNSIGNED_CHAR elif volume_array.dtype == np.int16: vtk_type = vtk.VTK_SHORT elif volume_array.dtype == np.uint16: vtk_type = vtk.VTK_UNSIGNED_SHORT elif volume_array.dtype == np.float32: vtk_type = vtk.VTK_FLOAT else: # 不支持的格式转换为float32 volume_array = volume_array.astype(np.float32) vtk_type = vtk.VTK_FLOAT # 转换为VTK数组 vtk_data = numpy_support.numpy_to_vtk( volume_array.ravel(), deep=True, array_type=vtk_type ) # 创建VTK图像 image = vtk.vtkImageData() image.SetDimensions(volume_array.shape) image.SetSpacing(spacing) image.SetOrigin(origin) image.GetPointData().SetScalars(vtk_data) return image class MedicalViewer(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("DICOM 三维可视化工具") self.setGeometry(100, 100, 1200, 800) self.current_path_points = [] self.path_planning_mode = False self.slice_views = {} self.init_ui() def init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QHBoxLayout(central_widget) # 左侧控制面板 control_panel = QWidget() control_layout = QVBoxLayout(control_panel) control_layout.setContentsMargins(5, 5, 5, 5) self.load_button = QPushButton("加载 DICOM 文件夹") self.load_button.clicked.connect(self.load_dicom) control_layout.addWidget(self.load_button) # 切片控制滑块 self.axial_slider = self.create_slice_control("轴向切片:") self.coronal_slider = self.create_slice_control("冠状切片:") self.sagittal_slider = self.create_slice_control("矢状切片:") control_layout.addWidget(self.axial_slider['container']) control_layout.addWidget(self.coronal_slider['container']) control_layout.addWidget(self.sagittal_slider['container']) # 窗宽窗位控制 self.ww_slider = self.create_window_control("窗宽:") self.wl_slider = self.create_window_control("窗位:") control_layout.addWidget(self.ww_slider['container']) control_layout.addWidget(self.wl_slider['container']) # 等值面阈值控制 self.threshold_slider = self.create_threshold_control("等值面阈值:") control_layout.addWidget(self.threshold_slider['container']) control_layout.addStretch() # 路径规划按钮 self.path_button = QPushButton("开始路径规划") self.path_button.clicked.connect(self.toggle_path_planning) self.path_button.setEnabled(False) control_layout.addWidget(self.path_button) self.clear_path_button = QPushButton("清除路径") self.clear_path_button.clicked.connect(self.clear_path) self.clear_path_button.setEnabled(False) control_layout.addWidget(self.clear_path_button) # 导出按钮 self.export_mesh_button = QPushButton("导出网格为 DAE") self.export_mesh_button.clicked.connect(self.export_mesh_to_dae) self.export_mesh_button.setEnabled(False) control_layout.addWidget(self.export_mesh_button) self.export_path_button = QPushButton("导出路径为 DAE") self.export_path_button.clicked.connect(self.export_path_to_dae) self.export_path_button.setEnabled(False) control_layout.addWidget(self.export_path_button) # 右侧显示区域 display_panel = QWidget() display_layout = QVBoxLayout(display_panel) # 2D 切片显示 self.figure, self.axes = plt.subplots(1, 3, figsize=(12, 4)) self.figure.subplots_adjust(left=0.02, right=0.98, bottom=0.02, top=0.95, wspace=0.05, hspace=0) self.canvas = FigureCanvas(self.figure) display_layout.addWidget(self.canvas) # 初始化2D视图 self.slice_views["axial"] = { "axis": self.axes[0], "slider": self.axial_slider['slider'] } self.slice_views["coronal"] = { "axis": self.axes[1], "slider": self.coronal_slider['slider'] } self.slice_views["sagittal"] = { "axis": self.axes[2], "slider": self.sagittal_slider['slider'] } # 连接信号 for view in self.slice_views.values(): view["axis"].axis("off") view["image"] = None view["slider"].valueChanged.connect(self.update_slice_views) # 3D VTK 渲染窗口 self.vtk_widget = QVTKRenderWindowInteractor() display_layout.addWidget(self.vtk_widget) main_layout.addWidget(control_panel, stretch=1) main_layout.addWidget(display_panel, stretch=4) def create_slice_control(self, label_text): """创建切片控制滑块""" container = QWidget() layout = QVBoxLayout(container) layout.setContentsMargins(0, 0, 0, 0) label = QLabel(label_text) layout.addWidget(label) slider = QSlider(Qt.Horizontal) slider.setEnabled(False) layout.addWidget(slider) return {'container': container, 'slider': slider} def create_window_control(self, label_text): """创建窗宽窗位控制滑块""" container = QWidget() layout = QVBoxLayout(container) layout.setContentsMargins(0, 0, 0, 0) label = QLabel(label_text) layout.addWidget(label) slider = QSlider(Qt.Horizontal) slider.setRange(0, 4000) slider.setValue(2000) slider.setEnabled(False) slider.valueChanged.connect(self.apply_window_level) layout.addWidget(slider) return {'container': container, 'slider': slider} def create_threshold_control(self, label_text): """创建等值面阈值控制滑块""" container = QWidget() layout = QVBoxLayout(container) layout.setContentsMargins(0, 0, 0, 0) label = QLabel(label_text) layout.addWidget(label) slider = QSlider(Qt.Horizontal) slider.setRange(-1000, 1000) slider.setValue(500) slider.setEnabled(False) slider.valueChanged.connect(self.update_3d_renderer) layout.addWidget(slider) return {'container': container, 'slider': slider} def load_dicom(self): """加载DICOM文件夹""" directory = QFileDialog.getExistingDirectory(self, "选择 DICOM 文件夹") if not directory: return # 创建进度对话框 progress_dialog = QProgressDialog("正在加载DICOM文件...", "取消", 0, 100, self) progress_dialog.setWindowTitle("加载中") progress_dialog.setWindowModality(Qt.WindowModal) progress_dialog.setAutoClose(True) # 创建并启动加载线程 self.loader = DICOMLoader(directory) self.loader.progress_updated.connect(progress_dialog.setValue) self.loader.loading_complete.connect(self.on_dicom_loaded) self.loader.loading_failed.connect(lambda msg: ( progress_dialog.cancel(), QMessageBox.critical(self, "错误", msg) )) self.loader.finished.connect(progress_dialog.deleteLater) self.loader.start() def on_dicom_loaded(self, volume_array, spacing, origin, vtk_image): """DICOM加载完成后的处理""" self.volume_array = volume_array self.vtk_image = vtk_image self.spacing = spacing self.origin = origin # 设置滑块范围 self.axial_slider['slider'].setRange(0, volume_array.shape[0] - 1) self.coronal_slider['slider'].setRange(0, volume_array.shape[1] - 1) self.sagittal_slider['slider'].setRange(0, volume_array.shape[2] - 1) # 启用滑块 self.axial_slider['slider'].setEnabled(True) self.coronal_slider['slider'].setEnabled(True) self.sagittal_slider['slider'].setEnabled(True) self.threshold_slider['slider'].setEnabled(True) # 设置初始位置 self.axial_slider['slider'].setValue(volume_array.shape[0] // 2) self.coronal_slider['slider'].setValue(volume_array.shape[1] // 2) self.sagittal_slider['slider'].setValue(volume_array.shape[2] // 2) # 启用窗宽窗位控制 self.ww_slider['slider'].setEnabled(True) self.wl_slider['slider'].setEnabled(True) # 初始化3D视图 self.setup_3d_renderer() # 更新2D视图 self.update_slice_views() # 启用其他按钮 self.path_button.setEnabled(True) self.export_mesh_button.setEnabled(True) def setup_3d_renderer(self): """初始化3D渲染器""" self.renderer = vtk.vtkRenderer() self.vtk_widget.GetRenderWindow().AddRenderer(self.renderer) self.interactor = self.vtk_widget.GetRenderWindow().GetInteractor() # 初始3D重建 self.update_3d_renderer() # 设置背景和相机 self.renderer.SetBackground(0.2, 0.3, 0.4) self.renderer.ResetCamera() # 添加光源 light1 = vtk.vtkLight() light1.SetPosition(0, 0, 1) light1.SetFocalPoint(self.renderer.GetActiveCamera().GetFocalPoint()) self.renderer.AddLight(light1) light2 = vtk.vtkLight() light2.SetPosition(0, 1, 0) light2.SetFocalPoint(self.renderer.GetActiveCamera().GetFocalPoint()) self.renderer.AddLight(light2) # 初始化交互器 self.interactor.Initialize() self.interactor.Start() def update_3d_renderer(self): """更新3D重建""" if not hasattr(self, "vtk_image"): return # 移除旧的actor if hasattr(self, "mesh_actor"): self.renderer.RemoveActor(self.mesh_actor) # 获取当前阈值 threshold = self.threshold_slider['slider'].value() # Marching Cubes表面重建 marching_cubes = vtk.vtkMarchingCubes() marching_cubes.SetInputData(self.vtk_image) marching_cubes.SetValue(0, threshold) # 平滑滤波器 smoother = vtk.vtkWindowedSincPolyDataFilter() smoother.SetInputConnection(marching_cubes.GetOutputPort()) smoother.SetNumberOfIterations(20) smoother.BoundarySmoothingOn() smoother.FeatureEdgeSmoothingOff() smoother.SetPassBand(0.1) smoother.NonManifoldSmoothingOn() smoother.NormalizeCoordinatesOn() smoother.Update() # 创建mapper和actor mapper = vtk.vtkPolyDataMapper() mapper.SetInputConnection(smoother.GetOutputPort()) mapper.ScalarVisibilityOff() self.mesh_actor = vtk.vtkActor() self.mesh_actor.SetMapper(mapper) self.mesh_actor.GetProperty().SetColor(0.9, 0.75, 0.6) self.mesh_actor.GetProperty().SetOpacity(0.8) self.mesh_actor.GetProperty().SetSpecular(0.3) self.mesh_actor.GetProperty().SetSpecularPower(20) self.renderer.AddActor(self.mesh_actor) self.vtk_widget.GetRenderWindow().Render() # 保存平滑后的网格用于导出 self.smoothed_mesh = smoother.GetOutput() def update_slice_views(self): """更新所有切片视图""" if not hasattr(self, "volume_array"): return # 获取当前切片位置 axial_pos = self.axial_slider['slider'].value() coronal_pos = self.coronal_slider['slider'].value() sagittal_pos = self.sagittal_slider['slider'].value() # 更新轴向视图 axial_slice = self.volume_array[axial_pos, :, :] self.slice_views["axial"]["image"] = axial_slice self.slice_views["axial"]["axis"].clear() self.slice_views["axial"]["axis"].imshow(axial_slice.T, cmap="gray", origin="lower") self.slice_views["axial"]["axis"].set_title(f"轴向: {axial_pos}/{self.volume_array.shape[0]-1}") self.slice_views["axial"]["axis"].axis("off") # 更新冠状视图 coronal_slice = self.volume_array[:, coronal_pos, :] self.slice_views["coronal"]["image"] = coronal_slice self.slice_views["coronal"]["axis"].clear() self.slice_views["coronal"]["axis"].imshow(coronal_slice.T, cmap="gray", origin="lower") self.slice_views["coronal"]["axis"].set_title(f"冠状: {coronal_pos}/{self.volume_array.shape[1]-1}") self.slice_views["coronal"]["axis"].axis("off") # 更新矢状视图 sagittal_slice = self.volume_array[:, :, sagittal_pos] self.slice_views["sagittal"]["image"] = sagittal_slice self.slice_views["sagittal"]["axis"].clear() self.slice_views["sagittal"]["axis"].imshow(sagittal_slice.T, cmap="gray", origin="lower") self.slice_views["sagittal"]["axis"].set_title(f"矢状: {sagittal_pos}/{self.volume_array.shape[2]-1}") self.slice_views["sagittal"]["axis"].axis("off") # 应用窗宽窗位 self.apply_window_level() # 如果有路径点,在2D视图中显示 if hasattr(self, "current_path_points") and self.current_path_points: self.draw_path_on_slices() self.canvas.draw() def apply_window_level(self): """应用窗宽窗位设置""" if not hasattr(self, "volume_array"): return ww = self.ww_slider['slider'].value() wl = self.wl_slider['slider'].value() for view in self.slice_views.values(): if view["image"] is not None: for img in view["axis"].get_images(): img.set_clim(wl - ww/2, wl + ww/2) self.canvas.draw() def draw_path_on_slices(self): """在切片上绘制路径点""" if not self.current_path_points: return # 将世界坐标转换为图像坐标 for view_name, view in self.slice_views.items(): view["axis"].clear() # 重新绘制图像 if view["image"] is not None: view["axis"].imshow(view["image"].T, cmap="gray", origin="lower") view["axis"].axis("off") # 绘制路径点 for i, point in enumerate(self.current_path_points): # 转换为图像坐标 img_coord = (np.array(point) - self.origin) / self.spacing # 根据视图类型确定要显示的坐标 if view_name == "axial": x, y = img_coord[1], img_coord[2] # 注意坐标顺序 current_slice = self.axial_slider['slider'].value() if abs(img_coord[0] - current_slice) < 1.0: view["axis"].plot(x, y, "r+", markersize=10) view["axis"].text(x, y, str(i), color="red") elif view_name == "coronal": x, y = img_coord[0], img_coord[2] current_slice = self.coronal_slider['slider'].value() if abs(img_coord[1] - current_slice) < 1.0: view["axis"].plot(x, y, "r+", markersize=10) view["axis"].text(x, y, str(i), color="red") elif view_name == "sagittal": x, y = img_coord[0], img_coord[1] current_slice = self.sagittal_slider['slider'].value() if abs(img_coord[2] - current_slice) < 1.0: view["axis"].plot(x, y, "r+", markersize=10) view["axis"].text(x, y, str(i), color="red") def toggle_path_planning(self): """切换路径规划模式""" self.path_planning_mode = not self.path_planning_mode if self.path_planning_mode: self.path_button.setText("完成路径规划") self.clear_path_button.setEnabled(False) self.current_path_points = [] self.export_path_button.setEnabled(False) # 设置交互回调 self.interactor.AddObserver(vtk.vtkCommand.LeftButtonPressEvent, self.add_path_point) else: self.path_button.setText("开始路径规划") self.clear_path_button.setEnabled(len(self.current_path_points) > 0) # 移除交互回调 self.interactor.RemoveObservers(vtk.vtkCommand.LeftButtonPressEvent) if len(self.current_path_points) > 1: self.draw_3d_path() self.export_path_button.setEnabled(True) def add_path_point(self, obj, event): """添加路径点""" click_pos = self.interactor.GetEventPosition() # 使用拾取器获取3D坐标 picker = vtk.vtkCellPicker() picker.SetTolerance(0.005) picker.Pick(click_pos[0], click_pos[1], 0, self.renderer) if picker.GetCellId() != -1: world_pos = picker.GetPickPosition() self.current_path_points.append(world_pos) # 在2D视图中显示标记 self.draw_path_on_slices() self.canvas.draw() def draw_3d_path(self): """绘制3D路径""" if len(self.current_path_points) < 2: return # 如果已有路径,先移除 if hasattr(self, "path_actor"): self.renderer.RemoveActor(self.path_actor) # 创建路径线条 points = vtk.vtkPoints() lines = vtk.vtkCellArray() lines.InsertNextCell(len(self.current_path_points)) for i, point in enumerate(self.current_path_points): points.InsertNextPoint(point) lines.InsertCellPoint(i) poly_data = vtk.vtkPolyData() poly_data.SetPoints(points) poly_data.SetLines(lines) # 创建顶点(用于显示点) vertices = vtk.vtkCellArray() for i in range(len(self.current_path_points)): vert = vtk.vtkVertex() vert.GetPointIds().SetId(0, i) vertices.InsertNextCell(vert) poly_data.SetVerts(vertices) # 创建mapper和actor mapper = vtk.vtkPolyDataMapper() mapper.SetInputData(poly_data) self.path_actor = vtk.vtkActor() self.path_actor.SetMapper(mapper) self.path_actor.GetProperty().SetColor(1, 0, 0) self.path_actor.GetProperty().SetLineWidth(3) self.path_actor.GetProperty().SetPointSize(8) self.renderer.AddActor(self.path_actor) self.vtk_widget.GetRenderWindow().Render() # 保存路径数据用于导出 self.path_data = poly_data def clear_path(self): """清除路径""" if hasattr(self, "path_actor"): self.renderer.RemoveActor(self.path_actor) del self.path_actor self.vtk_widget.GetRenderWindow().Render() self.current_path_points = [] self.clear_path_button.setEnabled(False) self.export_path_button.setEnabled(False) self.update_slice_views() def export_mesh_to_dae(self): """导出网格为DAE格式""" if not hasattr(self, "smoothed_mesh"): QMessageBox.warning(self, "警告", "没有可导出的网格") return options = QFileDialog.Options() file_path, _ = QFileDialog.getSaveFileName( self, "保存网格为 DAE 文件", "", "Collada 文件 (*.dae);;所有文件 (*)", options=options, ) if file_path: try: if not file_path.lower().endswith(".dae"): file_path += ".dae" # 创建导出器 exporter = vtk.vtkGLTFExporter() exporter.SetFileName(file_path) exporter.InlineDataOn() # 创建一个临时渲染窗口用于导出 render_window = vtk.vtkRenderWindow() renderer = vtk.vtkRenderer() render_window.AddRenderer(renderer) # 只添加网格actor renderer.AddActor(self.mesh_actor) renderer.SetBackground(0, 0, 0) exporter.SetRenderWindow(render_window) exporter.Write() QMessageBox.information(self, "成功", f"网格已保存到 {file_path}") except Exception as e: QMessageBox.critical(self, "错误", f"导出网格失败:\n{str(e)}") def export_path_to_dae(self): """导出路径为DAE格式""" if not hasattr(self, "path_data"): QMessageBox.warning(self, "警告", "没有可导出的路径") return options = QFileDialog.Options() file_path, _ = QFileDialog.getSaveFileName( self, "保存路径为 DAE 文件", "", "Collada 文件 (*.dae);;所有文件 (*)", options=options, ) if file_path: try: if not file_path.lower().endswith(".dae"): file_path += ".dae" # 创建路径的actor mapper = vtk.vtkPolyDataMapper() mapper.SetInputData(self.path_data) path_actor = vtk.vtkActor() path_actor.SetMapper(mapper) path_actor.GetProperty().SetColor(1, 0, 0) path_actor.GetProperty().SetLineWidth(3) path_actor.GetProperty().SetPointSize(8) # 创建导出器 exporter = vtk.vtkGLTFExporter() exporter.SetFileName(file_path) exporter.InlineDataOn() # 创建一个临时渲染窗口用于导出 render_window = vtk.vtkRenderWindow() renderer = vtk.vtkRenderer() render_window.AddRenderer(renderer) renderer.AddActor(path_actor) renderer.SetBackground(0, 0, 0) exporter.SetRenderWindow(render_window) exporter.Write() QMessageBox.information(self, "成功", f"路径已保存到 {file_path}") except Exception as e: QMessageBox.critical(self, "错误", f"导出路径失败:\n{str(e)}") def main(): app = QApplication(sys.argv) if hasattr(Qt, 'AA_EnableHighDpiScaling'): QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) if hasattr(Qt, 'AA_UseHighDpiPixmaps'): QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) viewer = MedicalViewer() viewer.show() sys.exit(app.exec_()) if __name__ == "__main__": if sys.platform == 'win32' and sys.executable.endswith('python.exe'): try: import subprocess subprocess.Popen([sys.executable.replace('python.exe', 'pythonw.exe')] + sys.argv) sys.exit(0) except: pass main() 该代码是通过读取DICOM文件并进行处理来实现CT图像的三维建模,但在使用过程中发现无法正确读取DICOM文件,所以我想要nii格式文件来进行图像处理实现CT图像的三维重建
07-12
import matplotlib matplotlib.use('Qt5Agg') # 使用 Qt5Agg 后端避免 X11 错误 import numpy as np import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import tkinter as tk from tkinter import Label, Scale, Entry, Button, Frame # ==================== 轨迹加载函数 ==================== def load_xy_trajectory(file_path): try: data = np.loadtxt(file_path) if data.ndim == 1: data = data.reshape(1, -1) x = data[:, 1] y = data[:, 2] return x, y except Exception as e: print(f"读取 {file_path} 失败: {e}") return np.array([]), np.array([]) # ==================== 坐标变换函数 ==================== def rotate_z_2d(x, y, angle_degrees): theta = np.radians(angle_degrees) cos_t, sin_t = np.cos(theta), np.sin(theta) x_rot = cos_t * x - sin_t * y y_rot = sin_t * x + cos_t * y return x_rot, y_rot def apply_transform(x, y, tx=0.0, ty=0.0, rz=0.0): x_rot, y_rot = rotate_z_2d(x, y, rz) return x_rot + tx, y_rot + ty # ==================== 主程序:GUI ==================== class TrajectoryGUI: def __init__(self, root): self.root = root self.root.title("轨迹对比可视化工具") # 加载数据 self.gnss_x, self.gnss_y = load_xy_trajectory('gnss_local_coords.txt') self.imu_x, self.imu_y = load_xy_trajectory('imu_odom.txt') if len(self.gnss_x) == 0 or len(self.imu_x) == 0: tk.Label(root, text="错误:无法加载轨迹数据,请检查文件路径和格式!", fg="red").pack() return # 初始化参数 self.tx = 0.0 self.ty = 0.0 self.rz = 0.0 self.flip_imu = False # 是否翻转 IMU 轨迹 # ========== 控制面板 ========== ctrl_frame = Frame(root) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10) Label(ctrl_frame, text="X 平移:", font=("Arial", 10)).grid(row=0, column=0, sticky='w') self.tx_entry = Entry(ctrl_frame) self.tx_entry.insert(0, "0.0") self.tx_entry.grid(row=0, column=1) Label(ctrl_frame, text="Y 平移:", font=("Arial", 10)).grid(row=1, column=0, sticky='w') self.ty_entry = Entry(ctrl_frame) self.ty_entry.insert(0, "0.0") self.ty_entry.grid(row=1, column=1) Label(ctrl_frame, text="Z 旋转 (°):", font=("Arial", 10)).grid(row=2, column=0, sticky='w') self.rz_entry = Entry(ctrl_frame) self.rz_entry.insert(0, "0.0") self.rz_entry.grid(row=2, column=1) Label(ctrl_frame, text="拖动调节:", font=("Arial", 10, "bold")).grid(row=3, column=0, columnspan=2, pady=10) self.tx_scale = Scale(ctrl_frame, from_=-5.0, to=5.0, resolution=0.01, orient='horizontal', length=200, label="X 平移", command=self.on_slider_change) self.tx_scale.grid(row=4, column=0, columnspan=2) self.ty_scale = Scale(ctrl_frame, from_=-5.0, to=5.0, resolution=0.01, orient='horizontal', length=200, label="Y 平移", command=self.on_slider_change) self.ty_scale.grid(row=5, column=0, columnspan=2) self.rz_scale = Scale(ctrl_frame, from_=-180, to=180, resolution=0.1, orient='horizontal', length=200, label="Z 旋转 (°)", command=self.on_slider_change) self.rz_scale.grid(row=6, column=0, columnspan=2) # 新增按钮 Button(ctrl_frame, text="🔄 翻转 IMU 轨迹", command=self.toggle_flip, bg="orange", font=("Arial", 10)).grid(row=7, column=0, columnspan=2, pady=5) Button(ctrl_frame, text="🔄 更新图像", command=self.update_plot, bg="lightblue", font=("Arial", 10)).grid(row=8, column=0, columnspan=2, pady=5) # ========== 图像区域 ========== fig, ax = plt.subplots(figsize=(8, 6)) self.fig = fig self.ax = ax canvas = FigureCanvasTkAgg(fig, root) canvas.get_tk_widget().pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) self.canvas = canvas # 初始绘图 self.update_plot() def toggle_flip(self): """切换 IMU 轨迹是否翻转""" self.flip_imu = not self.flip_imu self.update_plot() def on_slider_change(self, val): self.tx_entry.delete(0, tk.END) self.tx_entry.insert(0, f"{self.tx_scale.get():.3f}") self.ty_entry.delete(0, tk.END) self.ty_entry.insert(0, f"{self.ty_scale.get():.3f}") self.rz_entry.delete(0, tk.END) self.rz_entry.insert(0, f"{self.rz_scale.get():.3f}") self.update_plot(no_scale_update=True) def update_plot(self, no_scale_update=False): try: tx = float(self.tx_entry.get()) ty = float(self.ty_entry.get()) rz = float(self.rz_entry.get()) if not no_scale_update: self.tx_scale.set(tx) self.ty_scale.set(ty) self.rz_scale.set(rz) # 复制 IMU 数据并决定是否翻转 imu_x_use = -self.imu_x if self.flip_imu else self.imu_x imu_y_use = -self.imu_y if self.flip_imu else self.imu_y # 变换 IMU 轨迹 imu_x_trans, imu_y_trans = apply_transform(imu_x_use, imu_y_use, tx=tx, ty=ty, rz=rz) # 清空并重绘 self.ax.clear() self.ax.plot(self.gnss_x, self.gnss_y, label='GNSS 轨迹 (参考)', color='blue', linewidth=2) self.ax.plot(imu_x_trans, imu_y_trans, label='IMU-Odometry 轨迹', color='red', linestyle='--', linewidth=2) self.ax.scatter(imu_x_trans[0], imu_y_trans[0], color='green', s=100, label='IMU起点', zorder=5) self.ax.scatter(self.gnss_x[0], self.gnss_y[0], color='purple', s=100, label='GNSS起点', zorder=5) flip_status = "翻转" if self.flip_imu else "正常" self.ax.set_title(f'轨迹对比图 [{flip_status}]\n平移: ({tx:.3f}, {ty:.3f}), 旋转: {rz:.2f}°') self.ax.set_xlabel('X (m)') self.ax.set_ylabel('Y (m)') self.ax.axis('equal') self.ax.grid(True, alpha=0.3) self.ax.legend() self.canvas.draw() except ValueError: pass # ==================== 运行 ==================== if __name__ == "__main__": root = tk.Tk() app = TrajectoryGUI(root) root.mainloop()代码报错:guo@guo-Dell-G15-5520:~/tool/python脚本$ python duibi.py X Error of failed request: BadLength (poly request too large or internal Xlib length error) Major opcode of failed request: 139 (RENDER) Minor opcode of failed request: 20 (RenderAddGlyphs) Serial number of failed request: 241 Current serial number in output stream: 247
09-24
#参照这个程序设计一个GPS对抗程序,并给出全部代码 import sys import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QGroupBox, QLabel, QSlider, QComboBox, QPushButton, QDoubleSpinBox, QCheckBox, QFrame) from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QFont, QPalette, QColor import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure # 添加中文字体支持 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'SimSun', 'Arial Unicode MS'] # 设置中文字体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # ... 其余代码保持不变 ... class CommunicationSystem: def __init__(self): self.signal_freq = 100 # MHz self.signal_amplitude = 1.0 self.modulation_type = "FM" # FM, AM or PM self.modulation_index = 0.5 self.snr = 20 # dB self.noise_amplitude = 0.1 # 干扰参数 self.jamming_type = "None" # None, Noise, Sweep, Pulse, Tone self.jamming_amplitude = 0.5 self.jamming_freq = 110 # MHz self.jamming_sweep_start = 90 # MHz self.jamming_sweep_end = 110 # MHz self.jamming_sweep_rate = 0.5 # MHz/ms # 系统参数 self.sample_rate = 1000 # 采样率 (MS/s) self.duration = 1 # 信号持续时间 (ms) def generate_signal(self): """生成通信信号""" t = np.linspace(0, self.duration, int(self.sample_rate * self.duration)) carrier = np.sin(2 * np.pi * self.signal_freq * t) # 调制信号 if self.modulation_type == "FM": modulating_signal = np.sin(2 * np.pi * 1 * t) # 1 kHz调制信号 signal = np.sin(2 * np.pi * self.signal_freq * t + self.modulation_index * modulating_signal) elif self.modulation_type == "AM": modulating_signal = 0.5 * np.sin(2 * np.pi * 1 * t) + 0.5 signal = self.signal_amplitude * carrier * modulating_signal else: # PM modulating_signal = np.sin(2 * np.pi * 1 * t) signal = np.sin(2 * np.pi * self.signal_freq * t + self.modulation_index * modulating_signal) return t, signal def generate_noise(self, signal): """生成噪声""" noise_power = self.noise_amplitude * np.random.normal(0, 1, len(signal)) return signal + noise_power def generate_jamming(self, t): """生成干扰信号""" if self.jamming_type == "None": return np.zeros_like(t) elif self.jamming_type == "Noise": return self.jamming_amplitude * np.random.normal(0, 1, len(t)) elif self.jamming_type == "Tone": return self.jamming_amplitude * np.sin(2 * np.pi * self.jamming_freq * t) elif self.jamming_type == "Sweep": freq_sweep = np.linspace(self.jamming_sweep_start, self.jamming_sweep_end, len(t)) return self.jamming_amplitude * np.sin(2 * np.pi * freq_sweep * t) elif self.jamming_type == "Pulse": jamming_signal = np.zeros_like(t) pulse_width = 0.1 # 脉冲宽度 (ms) pulse_start = 0.3 # 脉冲开始时间 (ms) pulse_end = pulse_start + pulse_width pulse_indices = np.where((t >= pulse_start) & (t <= pulse_end))[0] jamming_signal[pulse_indices] = self.jamming_amplitude * np.sin( 2 * np.pi * self.jamming_freq * t[pulse_indices]) return jamming_signal return np.zeros_like(t) def generate_combined_signal(self): """生成带干扰的通信信号""" t, signal = self.generate_signal() noisy_signal = self.generate_noise(signal) jamming_signal = self.generate_jamming(t) combined_signal = noisy_signal + jamming_signal return t, signal, noisy_signal, jamming_signal, combined_signal def calculate_spectrum(self, signal): """计算信号的频谱""" n = len(signal) spectrum = np.fft.fft(signal) freq = np.fft.fftfreq(n, d=1.0 / self.sample_rate) return freq[:n // 2], np.abs(spectrum[:n // 2]) def calculate_snr(self, signal, noise): """计算信噪比""" signal_power = np.sum(signal ** 2) noise_power = np.sum(noise ** 2) if noise_power == 0: return float('inf') snr = 10 * np.log10(signal_power / noise_power) return snr class DigitalDashboard(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("超短波通信对抗仿真系统") self.setGeometry(100, 50, 1400, 900) # 初始化通信系统 self.comm_system = CommunicationSystem() # 设置深色主题 self.set_dark_theme() # 创建主控件 self.main_widget = QWidget() self.setCentralWidget(self.main_widget) # 主布局 main_layout = QVBoxLayout(self.main_widget) main_layout.setSpacing(15) main_layout.setContentsMargins(20, 20, 20, 20) # 标题 title_label = QLabel("超短波通信对抗仿真系统") title_label.setFont(QFont("Arial", 24, QFont.Bold)) title_label.setStyleSheet("color: #1E90FF;") title_label.setAlignment(Qt.AlignCenter) main_layout.addWidget(title_label) # 创建控制面板 control_panel = self.create_control_panel() main_layout.addWidget(control_panel) # 创建波形显示区域 waveform_panel = self.create_waveform_panel() main_layout.addWidget(waveform_panel, 1) # 创建频谱显示区域 spectrum_panel = self.create_spectrum_panel() main_layout.addWidget(spectrum_panel, 1) # 创建状态显示 status_panel = self.create_status_panel() main_layout.addWidget(status_panel) # 初始化数据 self.update_simulation() # 设置定时器用于实时更新 self.timer = QTimer(self) self.timer.timeout.connect(self.update_simulation) self.timer.start(100) # 每100ms更新一次 def set_dark_theme(self): """设置深色主题""" dark_palette = QPalette() dark_palette.setColor(QPalette.Window, QColor(25, 35, 45)) dark_palette.setColor(QPalette.WindowText, Qt.white) dark_palette.setColor(QPalette.Base, QColor(35, 45, 55)) dark_palette.setColor(QPalette.AlternateBase, QColor(45, 55, 65)) dark_palette.setColor(QPalette.ToolTipBase, Qt.white) dark_palette.setColor(QPalette.ToolTipText, Qt.white) dark_palette.setColor(QPalette.Text, Qt.white) dark_palette.setColor(QPalette.Button, QColor(50, 65, 80)) dark_palette.setColor(QPalette.ButtonText, Qt.white) dark_palette.setColor(QPalette.BrightText, Qt.red) dark_palette.setColor(QPalette.Highlight, QColor(30, 144, 255)) dark_palette.setColor(QPalette.HighlightedText, Qt.black) self.setPalette(dark_palette) self.setStyleSheet(""" QGroupBox { font-size: 14px; font-weight: bold; border: 2px solid #1E90FF; border-radius: 8px; margin-top: 1ex; color: #1E90FF; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top center; padding: 0 5px; } QSlider::groove:horizontal { border: 1px solid #4A708B; height: 8px; background: #2F4F4F; margin: 2px 0; border-radius: 4px; } QSlider::handle:horizontal { background: #1E90FF; border: 1px solid #4A708B; width: 18px; margin: -4px 0; border-radius: 9px; } QPushButton { background-color: #1E90FF; color: white; border: none; border-radius: 4px; padding: 5px 10px; font-weight: bold; } QPushButton:hover { background-color: #00BFFF; } QLabel { color: white; } """) def create_control_panel(self): """创建控制面板""" panel = QGroupBox("通信对抗控制面板") layout = QGridLayout() panel.setLayout(layout) # 通信参数控制 comm_label = QLabel("通信参数:") comm_label.setFont(QFont("Arial", 12, QFont.Bold)) layout.addWidget(comm_label, 0, 0) # 信号频率 freq_label = QLabel("信号频率 (MHz):") self.freq_spin = QDoubleSpinBox() self.freq_spin.setRange(30, 300) self.freq_spin.setValue(self.comm_system.signal_freq) self.freq_spin.setSingleStep(1) self.freq_spin.valueChanged.connect(self.update_freq) layout.addWidget(freq_label, 1, 0) layout.addWidget(self.freq_spin, 1, 1) # 信号幅度 amp_label = QLabel("信号幅度:") self.amp_slider = QSlider(Qt.Horizontal) self.amp_slider.setRange(0, 100) self.amp_slider.setValue(int(self.comm_system.signal_amplitude * 100)) self.amp_slider.valueChanged.connect(self.update_amp) layout.addWidget(amp_label, 2, 0) layout.addWidget(self.amp_slider, 2, 1) # 调制方式 mod_label = QLabel("调制方式:") self.mod_combo = QComboBox() self.mod_combo.addItems(["FM", "AM", "PM"]) self.mod_combo.setCurrentText(self.comm_system.modulation_type) self.mod_combo.currentTextChanged.connect(self.update_modulation) layout.addWidget(mod_label, 3, 0) layout.addWidget(self.mod_combo, 3, 1) # 调制指数 mod_idx_label = QLabel("调制指数:") self.mod_idx_slider = QSlider(Qt.Horizontal) self.mod_idx_slider.setRange(0, 100) self.mod_idx_slider.setValue(int(self.comm_system.modulation_index * 100)) self.mod_idx_slider.valueChanged.connect(self.update_mod_idx) layout.addWidget(mod_idx_label, 4, 0) layout.addWidget(self.mod_idx_slider, 4, 1) # 干扰参数控制 jam_label = QLabel("干扰参数:") jam_label.setFont(QFont("Arial", 12, QFont.Bold)) layout.addWidget(jam_label, 0, 2) # 干扰类型 jam_type_label = QLabel("干扰类型:") self.jam_type_combo = QComboBox() self.jam_type_combo.addItems(["None", "Noise", "Tone", "Sweep", "Pulse"]) self.jam_type_combo.setCurrentText(self.comm_system.jamming_type) self.jam_type_combo.currentTextChanged.connect(self.update_jam_type) layout.addWidget(jam_type_label, 1, 2) layout.addWidget(self.jam_type_combo, 1, 3) # 干扰幅度 jam_amp_label = QLabel("干扰幅度:") self.jam_amp_slider = QSlider(Qt.Horizontal) self.jam_amp_slider.setRange(0, 100) self.jam_amp_slider.setValue(int(self.comm_system.jamming_amplitude * 100)) self.jam_amp_slider.valueChanged.connect(self.update_jam_amp) layout.addWidget(jam_amp_label, 2, 2) layout.addWidget(self.jam_amp_slider, 2, 3) # 干扰频率 jam_freq_label = QLabel("干扰频率 (MHz):") self.jam_freq_spin = QDoubleSpinBox() self.jam_freq_spin.setRange(30, 300) self.jam_freq_spin.setValue(self.comm_system.jamming_freq) self.jam_freq_spin.setSingleStep(1) self.jam_freq_spin.valueChanged.connect(self.update_jam_freq) layout.addWidget(jam_freq_label, 3, 2) layout.addWidget(self.jam_freq_spin, 3, 3) # 扫频范围 sweep_start_label = QLabel("扫频起点 (MHz):") self.sweep_start_spin = QDoubleSpinBox() self.sweep_start_spin.setRange(30, 300) self.sweep_start_spin.setValue(self.comm_system.jamming_sweep_start) self.sweep_start_spin.setSingleStep(1) self.sweep_start_spin.valueChanged.connect(self.update_sweep_start) layout.addWidget(sweep_start_label, 4, 2) layout.addWidget(self.sweep_start_spin, 4, 3) sweep_end_label = QLabel("扫频终点 (MHz):") self.sweep_end_spin = QDoubleSpinBox() self.sweep_end_spin.setRange(30, 300) self.sweep_end_spin.setValue(self.comm_system.jamming_sweep_end) self.sweep_end_spin.setSingleStep(1) self.sweep_end_spin.valueChanged.connect(self.update_sweep_end) layout.addWidget(sweep_end_label, 5, 2) layout.addWidget(self.sweep_end_spin, 5, 3) # 系统参数 sys_label = QLabel("系统参数:") sys_label.setFont(QFont("Arial", 12, QFont.Bold)) layout.addWidget(sys_label, 0, 4) # 采样率 sample_rate_label = QLabel("采样率 (MS/s):") self.sample_rate_spin = QDoubleSpinBox() self.sample_rate_spin.setRange(100, 2000) self.sample_rate_spin.setValue(self.comm_system.sample_rate) self.sample_rate_spin.setSingleStep(100) self.sample_rate_spin.valueChanged.connect(self.update_sample_rate) layout.addWidget(sample_rate_label, 1, 4) layout.addWidget(self.sample_rate_spin, 1, 5) # 噪声幅度 noise_amp_label = QLabel("噪声幅度:") self.noise_amp_slider = QSlider(Qt.Horizontal) self.noise_amp_slider.setRange(0, 100) self.noise_amp_slider.setValue(int(self.comm_system.noise_amplitude * 100)) self.noise_amp_slider.valueChanged.connect(self.update_noise_amp) layout.addWidget(noise_amp_label, 2, 4) layout.addWidget(self.noise_amp_slider, 2, 5) # 控制按钮 self.reset_btn = QPushButton("重置参数") self.reset_btn.clicked.connect(self.reset_params) layout.addWidget(self.reset_btn, 6, 0, 1, 2) self.auto_jam_btn = QPushButton("自动干扰") self.auto_jam_btn.clicked.connect(self.toggle_auto_jam) layout.addWidget(self.auto_jam_btn, 6, 2, 1, 2) self.optimize_btn = QPushButton("优化通信") self.optimize_btn.clicked.connect(self.optimize_communication) layout.addWidget(self.optimize_btn, 6, 4, 1, 2) self.auto_jam_active = False return panel def create_waveform_panel(self): """创建波形显示面板""" panel = QGroupBox("通信波形与干扰波形") layout = QHBoxLayout() panel.setLayout(layout) # 原始信号波形图 self.signal_fig = Figure(figsize=(10, 4), dpi=100) self.signal_canvas = FigureCanvas(self.signal_fig) self.signal_ax = self.signal_fig.add_subplot(111) self.signal_ax.set_title("原始通信信号") self.signal_ax.set_xlabel("时间 (ms)") self.signal_ax.set_ylabel("幅度") self.signal_ax.grid(True, linestyle='--', alpha=0.6) layout.addWidget(self.signal_canvas) # 干扰信号波形图 self.jamming_fig = Figure(figsize=(10, 4), dpi=100) self.jamming_canvas = FigureCanvas(self.jamming_fig) self.jamming_ax = self.jamming_fig.add_subplot(111) self.jamming_ax.set_title("干扰信号") self.jamming_ax.set_xlabel("时间 (ms)") self.jamming_ax.set_ylabel("幅度") self.jamming_ax.grid(True, linestyle='--', alpha=0.6) layout.addWidget(self.jamming_canvas) # 合成信号波形图 self.combined_fig = Figure(figsize=(10, 4), dpi=100) self.combined_canvas = FigureCanvas(self.combined_fig) self.combined_ax = self.combined_fig.add_subplot(111) self.combined_ax.set_title("合成信号 (通信 + 干扰)") self.combined_ax.set_xlabel("时间 (ms)") self.combined_ax.set_ylabel("幅度") self.combined_ax.grid(True, linestyle='--', alpha=0.6) layout.addWidget(self.combined_canvas) return panel def create_spectrum_panel(self): """创建频谱显示面板""" panel = QGroupBox("信号频谱分析") layout = QHBoxLayout() panel.setLayout(layout) # 原始信号频谱图 self.spectrum_fig = Figure(figsize=(10, 4), dpi=100) self.spectrum_canvas = FigureCanvas(self.spectrum_fig) self.spectrum_ax = self.spectrum_fig.add_subplot(111) self.spectrum_ax.set_title("信号频谱") self.spectrum_ax.set_xlabel("频率 (MHz)") self.spectrum_ax.set_ylabel("幅度") self.spectrum_ax.grid(True, linestyle='--', alpha=0.6) layout.addWidget(self.spectrum_canvas) # 合成信号频谱图 self.combined_spectrum_fig = Figure(figsize=(10, 4), dpi=100) self.combined_spectrum_canvas = FigureCanvas(self.combined_spectrum_fig) self.combined_spectrum_ax = self.combined_spectrum_fig.add_subplot(111) self.combined_spectrum_ax.set_title("合成信号频谱") self.combined_spectrum_ax.set_xlabel("频率 (MHz)") self.combined_spectrum_ax.set_ylabel("幅度") self.combined_spectrum_ax.grid(True, linestyle='--', alpha=0.6) layout.addWidget(self.combined_spectrum_canvas) return panel def create_status_panel(self): """创建状态显示面板""" panel = QFrame() panel.setFrameShape(QFrame.StyledPanel) layout = QHBoxLayout() panel.setLayout(layout) # 通信状态 comm_status = QGroupBox("通信状态") comm_layout = QVBoxLayout() comm_status.setLayout(comm_layout) self.comm_status_label = QLabel("通信质量: 良好") self.comm_status_label.setFont(QFont("Arial", 14)) self.comm_status_label.setStyleSheet("color: #00FF00;") comm_layout.addWidget(self.comm_status_label) self.snr_label = QLabel("信噪比(SNR): 20 dB") self.snr_label.setFont(QFont("Arial", 12)) comm_layout.addWidget(self.snr_label) self.ber_label = QLabel("误码率(BER): 0.001%") self.ber_label.setFont(QFont("Arial", 12)) comm_layout.addWidget(self.ber_label) layout.addWidget(comm_status) # 干扰状态 jam_status = QGroupBox("干扰状态") jam_layout = QVBoxLayout() jam_status.setLayout(jam_layout) self.jam_status_label = QLabel("干扰强度: 弱") self.jam_status_label.setFont(QFont("Arial", 14)) self.jam_status_label.setStyleSheet("color: #FFA500;") jam_layout.addWidget(self.jam_status_label) self.jam_power_label = QLabel("干扰功率: 0.5") self.jam_power_label.setFont(QFont("Arial", 12)) jam_layout.addWidget(self.jam_power_label) self.jam_effect_label = QLabel("干扰效果: 轻微") self.jam_effect_label.setFont(QFont("Arial", 12)) jam_layout.addWidget(self.jam_effect_label) layout.addWidget(jam_status) # 系统状态 sys_status = QGroupBox("系统状态") sys_layout = QVBoxLayout() sys_status.setLayout(sys_layout) self.sys_status_label = QLabel("系统运行: 正常") self.sys_status_label.setFont(QFont("Arial", 14)) self.sys_status_label.setStyleSheet("color: #00FF00;") sys_layout.addWidget(self.sys_status_label) self.sample_rate_status = QLabel("采样率: 1000 MS/s") self.sample_rate_status.setFont(QFont("Arial", 12)) sys_layout.addWidget(self.sample_rate_status) self.freq_range_status = QLabel("频率范围: 30-300 MHz") self.freq_range_status.setFont(QFont("Arial", 12)) sys_layout.addWidget(self.freq_range_status) layout.addWidget(sys_status) return panel def update_simulation(self): """更新仿真数据并刷新界面""" # 生成信号数据 t, signal, noisy_signal, jamming_signal, combined_signal = self.comm_system.generate_combined_signal() # 计算频谱 freq, spectrum = self.comm_system.calculate_spectrum(signal) _, combined_spectrum = self.comm_system.calculate_spectrum(combined_signal) # 计算信噪比 snr = self.comm_system.calculate_snr(signal, jamming_signal + (noisy_signal - signal)) # 更新波形图 self.update_waveform_plot(t, signal, jamming_signal, combined_signal) # 更新频谱图 self.update_spectrum_plot(freq, spectrum, combined_spectrum) # 更新状态显示 self.update_status(snr, combined_signal) # 自动干扰模式 if self.auto_jam_active: self.auto_jamming() def update_waveform_plot(self, t, signal, jamming_signal, combined_signal): """更新波形图""" # 原始信号 self.signal_ax.clear() self.signal_ax.plot(t, signal, 'b-', linewidth=1.5) self.signal_ax.set_title("原始通信信号") self.signal_ax.set_xlabel("时间 (ms)") self.signal_ax.set_ylabel("幅度") self.signal_ax.grid(True, linestyle='--', alpha=0.6) self.signal_ax.set_xlim(0, self.comm_system.duration) self.signal_ax.set_ylim(-2.5, 2.5) self.signal_canvas.draw() # 干扰信号 self.jamming_ax.clear() self.jamming_ax.plot(t, jamming_signal, 'r-', linewidth=1.5) self.jamming_ax.set_title("干扰信号") self.jamming_ax.set_xlabel("时间 (ms)") self.jamming_ax.set_ylabel("幅度") self.jamming_ax.grid(True, linestyle='--', alpha=0.6) self.jamming_ax.set_xlim(0, self.comm_system.duration) self.jamming_ax.set_ylim(-2.5, 2.5) self.jamming_canvas.draw() # 合成信号 self.combined_ax.clear() self.combined_ax.plot(t, combined_signal, 'g-', linewidth=1.5) self.combined_ax.set_title("合成信号 (通信 + 干扰)") self.combined_ax.set_xlabel("时间 (ms)") self.combined_ax.set_ylabel("幅度") self.combined_ax.grid(True, linestyle='--', alpha=0.6) self.combined_ax.set_xlim(0, self.comm_system.duration) self.combined_ax.set_ylim(-2.5, 2.5) self.combined_canvas.draw() def update_spectrum_plot(self, freq, spectrum, combined_spectrum): """更新频谱图""" # 原始信号频谱 self.spectrum_ax.clear() self.spectrum_ax.plot(freq, spectrum, 'b-', linewidth=1.5) self.spectrum_ax.set_title("信号频谱") self.spectrum_ax.set_xlabel("频率 (MHz)") self.spectrum_ax.set_ylabel("幅度") self.spectrum_ax.grid(True, linestyle='--', alpha=0.6) self.spectrum_ax.set_xlim(0, 300) self.spectrum_canvas.draw() # 合成信号频谱 self.combined_spectrum_ax.clear() self.combined_spectrum_ax.plot(freq, combined_spectrum, 'g-', linewidth=1.5) self.combined_spectrum_ax.set_title("合成信号频谱") self.combined_spectrum_ax.set_xlabel("频率 (MHz)") self.combined_spectrum_ax.set_ylabel("幅度") self.combined_spectrum_ax.grid(True, linestyle='--', alpha=0.6) self.combined_spectrum_ax.set_xlim(0, 300) self.combined_spectrum_canvas.draw() def update_status(self, snr, combined_signal): """更新状态显示""" # 更新SNR self.snr_label.setText(f"信噪比(SNR): {snr:.2f} dB") # 估算误码率 ber = max(0.0001, 1 / (1 + np.exp(0.5 * (snr - 10)))) self.ber_label.setText(f"误码率(BER): {ber * 100:.4f}%") # 通信质量评估 if snr > 15: self.comm_status_label.setText("通信质量: 优秀") self.comm_status_label.setStyleSheet("color: #00FF00;") elif snr > 10: self.comm_status_label.setText("通信质量: 良好") self.comm_status_label.setStyleSheet("color: #7CFC00;") elif snr > 5: self.comm_status_label.setText("通信质量: 一般") self.comm_status_label.setStyleSheet("color: #FFD700;") else: self.comm_status_label.setText("通信质量: 差") self.comm_status_label.setStyleSheet("color: #FF4500;") # 干扰状态 jam_power = np.sum(np.abs(self.comm_system.jamming_amplitude)) self.jam_power_label.setText(f"干扰功率: {jam_power:.2f}") if jam_power < 0.3: self.jam_status_label.setText("干扰强度: 弱") self.jam_status_label.setStyleSheet("color: #7CFC00;") elif jam_power < 0.7: self.jam_status_label.setText("干扰强度: 中") self.jam_status_label.setStyleSheet("color: #FFA500;") else: self.jam_status_label.setText("干扰强度: 强") self.jam_status_label.setStyleSheet("color: #FF4500;") # 干扰效果评估 if snr < 5: self.jam_effect_label.setText("干扰效果: 严重") elif snr < 10: self.jam_effect_label.setText("干扰效果: 显著") elif snr < 15: self.jam_effect_label.setText("干扰效果: 轻微") else: self.jam_effect_label.setText("干扰效果: 无") # 系统状态 self.sample_rate_status.setText(f"采样率: {self.comm_system.sample_rate} MS/s") def auto_jamming(self): """自动干扰模式""" # 随机改变干扰参数 if np.random.rand() < 0.1: self.jam_type_combo.setCurrentIndex(np.random.randint(1, 5)) if np.random.rand() < 0.2: new_freq = max(30, min(300, self.comm_system.jamming_freq + np.random.randint(-20, 20))) self.jam_freq_spin.setValue(new_freq) if np.random.rand() < 0.3: new_amp = max(0.1, min(1.0, self.comm_system.jamming_amplitude + np.random.uniform(-0.1, 0.1))) self.jam_amp_slider.setValue(int(new_amp * 100)) def toggle_auto_jam(self): """切换自动干扰模式""" self.auto_jam_active = not self.auto_jam_active if self.auto_jam_active: self.auto_jam_btn.setText("停止干扰") self.auto_jam_btn.setStyleSheet("background-color: #FF4500;") else: self.auto_jam_btn.setText("自动干扰") self.auto_jam_btn.setStyleSheet("") def optimize_communication(self): """优化通信参数""" # 简单优化策略:避开干扰频率 if self.comm_system.jamming_type != "None" and self.comm_system.jamming_freq > 0: # 尝试避开干扰频率 new_freq = self.comm_system.jamming_freq + 20 if new_freq > 300: new_freq = self.comm_system.jamming_freq - 20 self.freq_spin.setValue(new_freq) self.comm_status_label.setText("通信参数已优化") def reset_params(self): """重置参数到默认值""" self.comm_system = CommunicationSystem() self.freq_spin.setValue(self.comm_system.signal_freq) self.amp_slider.setValue(int(self.comm_system.signal_amplitude * 100)) self.mod_combo.setCurrentText(self.comm_system.modulation_type) self.mod_idx_slider.setValue(int(self.comm_system.modulation_index * 100)) self.jam_type_combo.setCurrentText(self.comm_system.jamming_type) self.jam_amp_slider.setValue(int(self.comm_system.jamming_amplitude * 100)) self.jam_freq_spin.setValue(self.comm_system.jamming_freq) self.sweep_start_spin.setValue(self.comm_system.jamming_sweep_start) self.sweep_end_spin.setValue(self.comm_system.jamming_sweep_end) self.sample_rate_spin.setValue(self.comm_system.sample_rate) self.noise_amp_slider.setValue(int(self.comm_system.noise_amplitude * 100)) self.auto_jam_active = False self.auto_jam_btn.setText("自动干扰") self.auto_jam_btn.setStyleSheet("") # 参数更新函数 def update_freq(self, value): self.comm_system.signal_freq = value def update_amp(self, value): self.comm_system.signal_amplitude = value / 100.0 def update_modulation(self, text): self.comm_system.modulation_type = text def update_mod_idx(self, value): self.comm_system.modulation_index = value / 100.0 def update_jam_type(self, text): self.comm_system.jamming_type = text def update_jam_amp(self, value): self.comm_system.jamming_amplitude = value / 100.0 def update_jam_freq(self, value): self.comm_system.jamming_freq = value def update_sweep_start(self, value): self.comm_system.jamming_sweep_start = value def update_sweep_end(self, value): self.comm_system.jamming_sweep_end = value def update_sample_rate(self, value): self.comm_system.sample_rate = value def update_noise_amp(self, value): self.comm_system.noise_amplitude = value / 100.0 if __name__ == "__main__": app = QApplication(sys.argv) window = DigitalDashboard() window.show() sys.exit(app.exec_())
10-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值