当前位置: 首页 > news >正文

一文了解Cornerstone3D中窗宽窗位的3种设置场景及原理

🔆 引言

在使用Cornerstone3D渲染影像时,有一个常用功能“设置窗宽窗位(windowWidth&windowLevel)”,通过精确调整窗宽窗位,医生能够更清晰地区分各种组织,如区别软组织、骨骼、脑组织等。本文将围绕窗宽窗位的基础概念、如何使用工具调整及工具调整的实现原理、js动态调整、MPR视图下多视图同步调整等展开。

🔎 关于窗宽窗位

窗宽窗位在医学影像学中是一项重要概念,特别是在CT和MRI中。它们主要通过调整影像的对比度和亮度来改善组织的可视化,以便于更好的观察影像中不同组织的细节。所以在介绍如何设置窗宽窗位前,先简单说明下它们是什么。

窗宽(Window Width, WW)

窗宽是指在医学影像上可视化的灰度范围。它决定了影像中最黑和最白两个点之间的对比度。

  • 窗宽值越大,影像上显示的灰度差异就越小,对比度就越低

  • 窗宽值越小,影像上显示的灰度差异就越大,对比度就越高

窗位(Window Level, WL)

窗位是指影像中的中间灰度值,它决定了影像灰度范围的中心。通过调整窗位,可以改变影像的亮度,进而使某些结构更加明显。

  • 增加窗位值可以使影像整体变亮,有助于观察较深的结构

  • 减少窗位值可以使影像整体变暗,有助于观察较浅的结构。

为什么需要设置不同的窗宽窗位

医生或影像技师可以根据需要观察的组织类型选择合适的窗宽窗位设置,以下是医学中常用的窗宽窗位设置,所以我们在设计功能时一般会将常用数据设置为快捷操作,便于直接调整。

  • 脑窗: 窗宽(WW)约为 80-100 (HU),窗位(WL)约为 30-40 HU,用于优化灰质和白质的对比度,常用于检测脑部病变

  • 软组织窗:窗宽(WW)约为 300-500 HU (HU),窗位(WL)约为 40-60 HU,用于观察和区分身体软组织,如肌肉、器官等

  • 肺窗:窗宽(WW)约为 1500-2000 HU,窗位(WL)约为 -450 ~ -600 HU,用于观察肺部结构,能够清晰显示气道和肺实质

  • 骨窗:窗宽(WW)约为 1000-1500 HU,窗位(WL)约为 250-350 HU,用于观察骨骼的细节,常用于查找骨折和其他骨骼病变

  • 血管窗:窗宽(WW)约为 600-800 HU,窗位(WL)约为 120-160 HU,主要用于评估血管的情况,特别是在血管造影研究中

🪜 使用工具调整

在Cornerstone3D Tools中提供了调整窗宽窗位的工具 WindowLevelTool,操作应用于视图的WindowLevel。它提供了一种通过在图像上拖动鼠标来设置视窗的windowCenter和windowWidth的方法。

windowLevelTool 基础使用

部分关键代码,整体可运行代码可查看:在线演示

import {addTool,Enums as cstEnums,destroy as cstDestroy,ToolGroupManager,WindowLevelTool,
} from "@cornerstonejs/tools";// 声明注册激活工具的业务函数
addTools() {//  顶层API全局添加addTool(WindowLevelTool);// 创建工具组,在工具组添加const toolGroup = ToolGroupManager.createToolGroup(this.toolGroupId);toolGroup.addTool(WindowLevelTool.toolName);toolGroup.addViewport(this.viewportId1, this.renderingEngineId);toolGroup.addViewport(this.viewportId2, this.renderingEngineId);toolGroup.addViewport(this.viewportId3, this.renderingEngineId);// 设置当前激活的工具toolGroup.setToolActive(WindowLevelTool.toolName, {bindings: [{mouseButton: cstEnums.MouseBindings.Primary,},],});
}

WindowLevelTool 实现原理

在了解到WindowLevelTool如何使用后,那接下来我们来看一下它到底是如何执行的。

🧘 逻辑大纲梳理

在看具体的源码前,我们先大致梳理一下,如果想要在拖拽鼠标移动时更新窗宽位,我们都需要哪些数据?

  1. 当前窗宽和窗位值:调整时的起始点,dicom文件的元数据属性中通常包含当前的窗宽和窗位值,可以作为调整的初始值。

  2. 鼠标拖拽的位移数据: 水平方向的位移量和垂直方向的位移量,一般使用canvas的2D位移坐标,通常包含在事件监听中。

  3. 🚀 敏感度乘数(重点): 根据图像的动态范围,计算位移量对窗宽窗位的敏感度影响【这个是整个逻辑中重要且计算复杂的部分,具体实现逻辑在源码解读中展开】,

  4. 最新的窗宽窗位值:由以上三点计算出最新的窗宽窗位值,并赋值渲染

🏄 源码实现解读

在梳理完大致需要的数据后,我们再来看一下源码中是如何获取到这些数据,又有哪些数据是在初始梳理时被忽略掉的。

在 Cornerstone3D的官方github中找到 WindowLevelTool 这个文件,我们可以看到WindowLevelTool继承于BaseTool,但是这个不重要,不在本次讨论计划中,在整个类中,有一个 mouseDragCallback 函数,这个一看上去就像是关键函数,我们来看一下这个函数的实现。

核心目的:拿到最新的窗宽窗位值,并赋值影像渲染

👉 第一阶段:数据准备阶段

由以下流程图可见:在代码开始阶段,WindowLevelTool准备了deltaPointlowerupper(关于lower、upper与窗宽窗距地关系及转换方式在下一章节【动态调整方案】中详细展开)isPreScaledmodality 等变量,我们先来看整体的执行流程

根据上面的流程逻辑,我们对应着源码来具体看一下代码是如何实现的

👉 第二阶段:最新窗宽窗位计算阶段

经过上面的代码,我们已经拿到了计算新的窗宽窗位所需要的数据,那这些数据如何组合计算才可以得到新的窗宽窗位呢?

计算窗宽窗位比较核心的步骤是:计算敏感度比率,然后有比率值得到最新的窗宽窗位值,我们先来了解一下敏感度比率的计算逻辑,然后再看源码是如何通过编程实现这一计算逻辑的。

🔥 敏感度乘数计算逻辑

  1. 定义一个默认的敏感度乘数:在Cornerstone中这个值为4,const DEFAULT_MULTIPLIER = 4;

  2. 计算图像的动态范围

  • **获取动态范围:**动态范围一般指图像中像素值的最大值与最小值之间的差。对于CT图像,可以通过中间切片来获取

  • **动态范围与乘数的关系:**动态范围的大小可以用来改变乘数的计算

  1. 计算乘数
  • 一般乘数的计算为【(动态范围 || 2**元数据像素存储位置 取小)/默认动态范围】,const DEFAULT_IMAGE_DYNAMIC_RANGE = 1024;

🔥 最新窗宽窗位的计算逻辑

  1. 计算窗宽偏移量:由上面得到的敏感度乘数 * 鼠标在x轴上的偏移量,就能得到窗宽的一个偏移量

  2. 计算窗位偏移量: 由上面得到的敏感度乘数 * 鼠标在y轴上的偏移量,就能得到窗位的一个偏移量

  3. 计算最新的窗宽窗位:现在的窗宽窗位加上对应的偏移量,得到最新的窗宽窗位值

以上就是整个算法中比较核心的部分,那了解完计算逻辑后,我们来看一下在Cornerstone3D的源码中,是如何通过代码实现以上的计算逻辑的(由于篇幅问题,暂不展开说明PT模式下的实现,在后续PT工具文章中再展开说明)

👉 第三阶段:为视图设置新的窗宽窗位,并渲染

经过前两个阶段,我们已经拿到了最新的窗宽窗位值,现在我们只需要将最新的窗宽窗位值重新赋值给视图,并让视图重新渲染即可。

viewport.setProperties({voiRange: newRange,
});viewport.render();

如果当前Volume具有多个视图的话,需要多个视图都重新渲染一下

if (viewport instanceof VolumeViewport) {viewportsContainingVolumeUID.forEach((vp) => {if (viewport !== vp) {vp.render();}});return;}

至此,关于WindowLevelTools是如何设置窗宽窗位的源码已完全解读,现在大家应该基本了解了窗宽窗位都跟哪些数据相关,这些数据又是从哪里获取到的,获取到又是如何应用这些数据计算的(关于为什么能够事件的detail中获取到canvas的2d坐标的,会在后续事件监听文章中详细展开)

👩‍💻 动态调整方案

当我们在自己的项目中使用了WindowLevelTool,并成功激活了它,可以让用户自主调整窗宽窗距,这时产品又提出了一个新的需求,不能只让用户通过工具拖拽调整,我们应该内置一些常用的窗宽窗位让用户快速且精准的设置。

这个需求你拍脑袋一想,那直接设置几个快捷按钮不就可以了,但是快捷按钮是响应事件是什么,上面源码解读时获取到的lowerupper 与窗宽窗位又有什么关系?

lower 与 upper

在医学影像处理时,“lower”和“upper”通常指的是窗宽调整的下限和上限值。这些值定义了在图像显示时用于映射像素值到显示器亮度的范围。

  • Lower (下限):指的是窗宽调整范围的最小边界,计算公式通常是 WL - WW/2,这里的 WL 是窗位,WW 是窗宽。

  • Upper (上限):指的是窗宽调整范围的最大边界,计算公式通常是 WL + WW/2。

如何获取lower和upper

当我们知道lowerupper与窗宽窗位的计算关系后,我们就可以在拿到lower&upper后计算对应的窗宽窗位了,其实对于如何获取到lower&upper在上面WindowLevelTool的源码中已经给出来了,它在viewport的属性中

const enabledElement = getEnabledElement(element);
const { renderingEngine, viewport } = enabledElement; // 获取viewport的方式可以依据上下文多种方案获取const properties = viewport.getProperties(); // 获取到viewport的属性对象:properties
const { lower, upper } = properties.voiRange; // 从 properties 的voiRange属性中获取到当前视图中的 lower, upper

转换lower和upper

我们知道了(lower&upper)与(ww&wl)之间的计算方式后,虽然可以手动计算对应的 ww&wl ,但是Cornerstone本身提供了两个内置工具方法供我们转换使用

  • 由 lower&upper 转 ww&wl
  let { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower,upper
);
  • 由 ww&wl 转 lower&upper
 let { lower, upper } = utilities.windowLevel.toLowHighRange(windowWidth, windowCenter)

假设我们已经有了按钮设置对应的窗宽窗位,以下为Vue项目中MPR视图下每个按钮对应的点击事件示例:

// windowWidth,windowLevel 为当前按钮需要设置的窗框窗距
handleWindowLevelClick(windowWidth, windowLevel) {if (windowWidth && windowWidth) {const { lower, upper } = csUtils.windowLevel.toLowHighRange(windowWidth, windowLevel);[viewportId1, viewportId2,viewportId3].forEach((id) => {const vp = this.renderingEngine.getViewport(id);vp.setProperties({voiRange: {lower,upper,},});vp.render();});}},

内置函数源码解读

虽然在上面给出了lowerupper的通用计算方式,但是在处理Dicom文件时,Dicom标准已经明确给出了相关的计算方式,具体原理可查看 https://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.11.2.1.2,在内置的工具函数中使用的计算方式即Dicom标准中给出的计算方式。

对应源码地址:https://github.com/cornerstonejs/cornerstone3D/blob/bc54ae70cb2180d5ce42cc7eaa17633f0bb5f34a/packages/core/src/utilities/windowLevel.ts

toLowHighRange

function toLowHighRange(windowWidth: number,windowCenter: number
): {lower: number;upper: number;
} {const lower = windowCenter - 0.5 - (windowWidth - 1) / 2;const upper = windowCenter - 0.5 + (windowWidth - 1) / 2;return { lower, upper };
}

toWindowLevel

function toWindowLevel(low: number,high: number
): {windowWidth: number;windowCenter: number;
} {// Allow for swapping high/lowconst windowWidth = Math.abs(high - low) + 1;const windowCenter = (low + high + 1) / 2;return { windowWidth, windowCenter };
}

计算方式浅析

🤔 在计算lowerupper 时为什么窗宽 -1 ?

窗宽定义为要显示的灰度范围的宽度。在考虑窗的两端时,减去 1 是为了确保窗宽覆盖的是指定的像素范围内完整的单位数,减去的是开始的中心点。例如我们想要一个6个单位的窗宽时,减1主要是如下进行的:

  • 准确的窗边界定位:窗宽为6意味着从窗位中心开始,向每侧扩展出去的范围一共涵盖6个单位。在不减1的情况下,如果直接将窗宽的一半加/减到窗位上,可能会导致计算的范围实际上比预期宽或窄,因为这种计算可能不会精确考虑到窗位中心所在的那一个单位。

  • 确保窗宽精确覆盖期望的单位数:通过减去1后再除以2,实际上是在计算从窗位中心点向两边扩展时,确切地排除了中心点占用的那一个单位,然后均匀分配剩余的窗宽到中心点的两侧。这样做确保了,不管窗位中心点如何定位,从中心点向两侧扩展出的范围总是精确地覆盖了除中心点外的额外5个单位,从而确保整个窗宽为6个单位。

    🤔 在计算lowerupper 时为什么窗位 - 0.5 ?

窗位减去0.5,是为了在计算时能够处理半个像素单位的偏移,这样做有助于更精确地定位和调整图像窗的中心。

这种微调主要是考虑到像素值通常是整数,而窗宽和窗位的调整可能需要更细致的控制,特别是在灰度值的分布和转换过程中。减去0.5是一种常用的技巧,以确保在离散的像素值和连续的窗宽调整之间达到更好的对应和平滑过渡。

📡 多视图同步

当我们终于搞定动态设置窗宽窗距后,产品又又又又提了个需求:在MPR视图时,调整其中一个视图的窗宽窗位,其他两个要同步响应💥

听完这个需求后,第一反应是这还不简单,我都知道怎么动态设置了,设置个同步还不是手到擒来,

  • 先监听每个视图的VOI变化

  • 当他变化时将拿到的窗宽窗位动态设置给其他视图

但是这么一想,一方面要监听多个视图,还容易一不小心就陷入个死循环,有没有更好的实现方式呢?当然有:那就是之前提的同步器,以下为示例代码

import {SynchronizerManager,synchronizers,
} from '@cornerstonejs/tools';// 使用内置的createVOISynchronizer,创建一个VOI同步器
synchronizers.createVOISynchronizer(VOI_SYNCHRONIZER_ID);// 获取创建的VOI同步器
const voiSynchronizer = SynchronizerManager.getSynchronizer(VOI_SYNCHRONIZER_ID);// 为同步器添加同步视图[viewportid1, viewportid2, viewportid3].forEach((viewportId) => {voiSynchronizer.add({renderingEngineId,viewportId,});
});

这样我们就为每个视图添加了同步,当变化的时候会同步变化(由于篇幅问题,这里就不展开详细讲同步器相关源码实现了,会在后续自定义同步器中展示详说)

🎉 结语

到这里,窗宽窗位相关的知识点、3种场景下的设置方案及源码解读就介绍,欢迎交流沟通任何Cornerstone3D相关知识点 👏

本系列为从0上手Cornerstone3D系列文章,包括cornerstone核心概念、基础使用、常见案例、工具使用、运行原理、源码解读等等,欢迎Start演示Github:https://github.com/jianyaoo/vue-cornerstone-demo 交流更多相关使用技巧~

  • CornerStone3D核心概念:https://juejin.cn/post/7326432875955798027
  • Cornerstone3DTools常用工具:https://juejin.cn/post/7330300019022495779

相关文章:

一文了解Cornerstone3D中窗宽窗位的3种设置场景及原理

🔆 引言 在使用Cornerstone3D渲染影像时,有一个常用功能“设置窗宽窗位(windowWidth&windowLevel)”,通过精确调整窗宽窗位,医生能够更清晰地区分各种组织,如区别软组织、骨骼、脑组织等。…...

部署 LVS(nginx)+keepalived高可用负载均衡集群

目录 一、集群的概述 1、什么是集群 2、普通集群与负载均衡集群 2.1 普通集群(Regular Cluster) 2.2 负载均衡集群(Load Balancing Cluster) 2.3 高可用集群(High Availability Cluster) 2.4 区别 …...

Qt/QML编程之路:fork、vfork、exec、clone的对比及使用(46)

前言: 系统调用system call是OS提供的服务提供接口。系统调用fork()、vfork()、exec()和clone()都用于创建和操作进程。Linux下Qt编程也会用到vfork进行多进程间通信。让我们看一下以下每个系统调用的概述和比较: fork()、vfork()和clone()的工作原理相似,但在处…...

Go语言框架路由Controller控制器设计思路gin路由根据控制器目录分层生成路由地址

Controller设计好处 框架设计用controller分请求路由层级,应用从app目录开始对应请求url路由地址,这样设计师方便开发时候通过请求地址层级快速定位接口方法对应的代码位置。 例如api接口请求路径为:​​http://localhost:8110/​​busines…...

突破编程_C++_设计模式(责任链模式)

1 责任链模式的概念 责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许对象以链式的方式组织起来,以便对请求进行处理。这种模式为多个对象处理同一请求提供了一个灵活的机制,而无需在发送者和多…...

php开发100问?

什么是 PHP?PHP 是一种什么类型的语言?PHP 的优缺点是什么?如何在服务器上配置 PHP?PHP 中的变量是如何声明和使用的?如何在 PHP 中输出文本和变量?什么是 PHP 的数据类型?如何在 PHP 中实现条件…...

flink实战--Flink任务资源自动化优化

背景 在生产环境Flink任务资源是用户在实时平台端进行配置,用户本身对于实时任务具体配置多少资源经验较少,所以存在用户资源配置较多,但实际使用不到的情形。比如一个 Flink 任务实际上 4 个并发能够满足业务处理需求,结果用户配置了 16 个并发,这种情况会导致实时计算资…...

tsv文件在大数据技术栈里的应用场景

是的,\t 是指制表符(tab),它通常用作字段分隔符在 TSV(Tab-Separated Values)格式的文件中。TSV是一种简单的文本格式,它使用制表符来分隔每一列中的值,而每一行则代表一个数据记录。…...

vscode设置setting.json

{ // vscode默认启用了根据文件类型自动设置tabsize的选项 "editor.detectIndentation": false, // 重新设定tabsize "editor.tabSize": 2, // #每次保存的时候自动格式化 // "editor.formatOnSave": true, // #每次保存的时候将代码按eslint格式…...

Docker的安装及镜像加速的配置

文章目录 一.切换到root二.卸载旧版docker三.配置docker的yum库四.安装Docker五.Docker的启动和验证六.配置Docker阿里云镜像加速(全程免费) 该文章文章演示在Linux系统中安装docker,Windows安装docker请参考以下文章 Windows系统中安装docker及镜像加速的配置 一…...

AIGC时代IT人的迷茫有解(1):从“商业画布”到“个人画布”

IT人的迷茫和心态调整 最近打开新闻,各种IT老大都在说“AIGC时代,只要会说话,人人都会具备程序员的能力”,身边也有很多程序员朋友也已经在用GPT类的产品编程了。随着AIGC的发展,除了程序员,可能很多职业都会被替代或…...

Qt/QML编程之路:openglwidget和倒车影像的切换(43)

关于如何实现一个基于OpenGL的3d 图形,这个有很多专门的介绍,我在开发中遇到了这么一个问题: 如何实现一个倒车影像的video显示与一个3D物体显示的切换,因为开窗在同样的一个位置,如果车子倒车启动,则需要将原本显示3D的地方切换为视频图像的显示。 class testOpenGl : …...

Spring 初学者遇到的问题

TagLibraryValidator Spring 实战 5.2 中有个表单需要在 jsp 中遍历数组&#xff0c;添加&#xff1a;<% taglib uri"http://java.sun.com/jsp/jstl/core" prefix"c" %>&#xff0c;访问时发现有些问题&#xff1a; java.lang.NoClassDefFoundError…...

前端解决跨域问题( 6种方法 )

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…...

Linux 理解进程

目录 一、基本概念 二、描述进程-PCB 1、task_struct-PCB的一种 2、task_ struct内容分类 三、组织进程 四、查看进程 1、ps指令 2、top命令 3、/proc文件系统 4、在/proc文件中查看指定进程 5、进程的工作目录 五、通过系统调用获取进程标示符 1、getpid()/get…...

鸿蒙App基础

基础说明 .1、应用模型 .1.1、构成要素 应用组件 应用组件是应用的基本组成单位&#xff0c;是应用的运行入口。用户启动、使用和退出应用过程中&#xff0c;应用组件会在不同的状态间切换&#xff0c;这些状态称为应用组件的生命周期。应用组件提供生命周期的回调函数&…...

算法部署优化工程师面试题整理

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;C/C面试整理 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 目录 整体情况简介 高性能计算基础 AI 框架知识 算…...

C#,入门教程(26)——数据的基本概念与使用方法

上一篇&#xff1a; C#&#xff0c;入门教程(25)——注释&#xff08;Comments&#xff09;你会吗&#xff1f;看多图演示&#xff0c;学真正注释。https://blog.csdn.net/beijinghorn/article/details/124681888 本文所述的知识基本上适用于C/C&#xff0c;java等其他语言。 …...

加密与安全_PGP、OpenPGP和GPG加密通信协议

文章目录 PGPOpenPGPGPG工作原理工作流程用途案例说明过程 代码实现pom依赖PgpEncryptionUtilPgpDecryptionUtilCommonUtilsPgpEncryptionTest 小结 PGP PGP (Pretty Good Privacy) 是一种加密通信协议&#xff0c;用于保护电子邮件和文件的安全性和隐私。它通过使用加密、数字…...

Maven模块化最佳实践

一&#xff0c;模块化的原因及意义 模块化是一种将大型的软件系统拆分成相互独立的模块的方法。具有以下优势&#xff1a; 代码复用&#xff1a;不同的模块可以共享相同的代码。这样可以避免重复编写相同的代码&#xff0c;提高开发效率。 模块独立性&#xff1a;每个模块都可…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)

引言 在人工智能飞速发展的今天&#xff0c;大语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为技术领域的焦点。从智能写作到代码生成&#xff0c;LLM 的应用场景不断扩展&#xff0c;深刻改变了我们的工作和生活方式。然而&#xff0c;理解这些模型的内部…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...