网站建设前的需求分析/重庆网站优化

网站建设前的需求分析,重庆网站优化,制作安卓app的软件,网站死链怎么处理文章目录 什么是CBCTCBCT技术路线使用第三方工具使用Python实现使用前端实现 纯前端实现方案优缺点使用VolView实现CBCT VolView的使用1.克隆代码2.配置依赖3.运行4.效果 进阶:VolView配合Python解决卡顿1.修改VtkThreeView.vue2.新增Custom3DView.vue3.Python生成s…

文章目录

  • 什么是CBCT
  • CBCT技术路线
    • 使用第三方工具
    • 使用Python实现
    • 使用前端实现
  • 纯前端实现方案优缺点
    • 使用VolView实现CBCT
  • VolView的使用
    • 1.克隆代码
    • 2.配置依赖
    • 3.运行
    • 4.效果
  • 进阶:VolView配合Python解决卡顿
    • 1.修改VtkThreeView.vue
    • 2.新增Custom3DView.vue
    • 3.Python生成stl三维文件
    • 4.最终效果

什么是CBCT

放射科影像是医学软件必不可少的一部分,对影像的显示、编辑、处理等操作更是重点。在多种放射科影像中,CBCT是关键的一环。CBCT全称为口腔颌面锥形束CT,其工作原理是通过锥形X射线束围绕患者头部旋转扫描,结合计算机算法生成高分辨率的三维图像。

CBCT在口腔医学中几乎覆盖所有亚专科:
​种植牙:评估颌骨密度、神经管位置,辅助种植体定位和手术导板设计。
​正畸与阻生牙:观察牙齿排列、埋伏牙位置及与周围组织关系,减少拔牙风险。
​牙体牙髓治疗:诊断复杂根管、根裂及根尖病变,提高治疗精确性。
​颌面外科:用于肿瘤、骨折的术前评估及术后效果监测。
​颞下颌关节:清晰显示关节结构异常,辅助诊断关节紊乱病。>

CBCT技术路线

使用第三方工具

有不少工具可以实现CBCT效果,例如:Slicer。
在这里插入图片描述

使用Python实现

使用pydicom结合mpl_toolkits实现三维展示。

使用前端实现

使用VTK.js、Three.js及WebAssembly 实现。

方案技术适用场景优缺点
VTK.jsWebGL + VTK医学影像可视化(CT/MRI)高质量、原生支持 Volume Rendering,但数据转换复杂
Three.js + 3D 纹理WebGL + Shader一般 3D 可视化兼容性好,适合前端开发,但医学精度低
WebAssembly + 医学引擎WASM专业医学影像专业级医学软件,性能强,但开发难度大

纯前端实现方案优缺点

类别优点缺点
性能与成本1. 低服务器依赖,节省硬件和维护成本。
2. 实时交互,响应延迟低。
1. 浏览器内存限制大,可能崩溃。
2. 低端设备GPU性能不足,导致渲染卡顿。
数据隐私1. 数据无需上传服务器,符合隐私法规(如HIPAA)。
2. 离线缓存支持断网使用。
1. 数据预处理依赖后端,可能需临时暴露敏感信息。
功能与兼容性1. 支持基础三维操作(旋转、缩放、剖面切割)。1. 复杂算法(如深度学习分割)难以实现。
2. 浏览器兼容性有限(如旧版Safari)。
开发与部署1. 部署便捷,前端静态资源可托管至CDN。
2. 适合轻量级应用(教育、预览)。
1. 大规模数据加载耗时(如全头颅CBCT)。
2. 需额外优化压缩和分块加载逻辑。

使用VolView实现CBCT

VolView是一款基于VTK.js的开源医学影像浏览器,支持在网页端直接拖拽加载DICOM数据并生成2D切片及3D电影级体渲染视图,提供标注、测量等工具,所有数据均在本地处理,确保隐私安全。无需安装软件,可跨平台使用,适用于临床诊断与科研教育。
在这里插入图片描述

官网:https://volview.kitware.com/

关于VolView的介绍可以参考视频:

VolView的使用

1.克隆代码

GitHub地址:https://github.com/Kitware/VolView

2.配置依赖

npm i

3.运行

npm run dev

4.效果

在这里插入图片描述
注:点击左侧可以在线加载演示数据,也可以点击右侧上传本地dicom影像文件。

在这里插入图片描述

进阶:VolView配合Python解决卡顿

上文我们提到,基于vtk.js的纯前端CBCT解决方案,虽然能不依赖其他第三方软件的情况下显示出我们需要的效果,但它对性能的高要求导致打开前端的电脑必须有较高的GPU配置,否则将异常卡顿。

此处给出思路:

由于卡顿主要是三维显示导致,其代码需实时计算得出三维效果,导致浏览器卡顿。要解决卡顿,我们就需要解决三维显示问题。

我们可以将VolView的三维渲染部分替换为server端Python生成stl文件。

1.修改VtkThreeView.vue

在这里插入图片描述
去除原有渲染三维的组件,改为我们自定义的新组件: <Custom3DView />

全部代码如下:

<template><div class="vtk-container-wrapper vtk-three-container"><div class="vtk-container" :class="active ? 'active' : ''"><!-- 此处是绘制3D重建的地方 start--><div class="vtk-sub-container"><!-- <divclass="vtk-view"ref="vtkContainerRef"data-testid="vtk-view vtk-three-view"></div> --><Custom3DView /></div><!-- 此处是绘制3D重建的地方 end --><div class="overlay-no-events tool-layer"><crop-tool :view-id="viewID" /><pan-tool :viewId="viewID" /></div><view-overlay-grid class="overlay-no-events view-annotations"><template v-slot:top-left><div class="annotation-cell"><v-btnclass="pointer-events-all"darkiconsize="medium"variant="text"@click="resetCamera"><v-icon size="medium" class="py-1">mdi-camera-flip-outline</v-icon><v-tooltiplocation="right"activator="parent"transition="slide-x-transition">Reset Camera</v-tooltip></v-btn><span class="ml-3">{{ topLeftLabel }}</span></div></template></view-overlay-grid><transition name="loading"><div v-if="isImageLoading" class="overlay-no-events loading"><div>Loading the image</div><div><v-progress-circular indeterminate color="blue" /></div></div></transition></div></div>
</template><script lang="ts">
import {computed,defineComponent,onBeforeUnmount,onMounted,PropType,provide,ref,toRefs,watch,Ref,nextTick,
} from 'vue';
import { computedWithControl } from '@vueuse/core';
import { vec3 } from 'gl-matrix';import vtkVolumeRepresentationProxy from '@kitware/vtk.js/Proxy/Representations/VolumeRepresentationProxy';
import { Mode as LookupTableProxyMode } from '@kitware/vtk.js/Proxy/Core/LookupTableProxy';
import vtkPiecewiseFunctionProxy from '@kitware/vtk.js/Proxy/Core/PiecewiseFunctionProxy';
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
import { getDiagonalLength } from '@kitware/vtk.js/Common/DataModel/BoundingBox';
import type { Vector3 } from '@kitware/vtk.js/types';import { useProxyManager } from '@/src/composables/useProxyManager';
import ViewOverlayGrid from '@/src/components/ViewOverlayGrid.vue';
import { useResizeObserver } from '../composables/useResizeObserver';
import { useCurrentImage } from '../composables/useCurrentImage';
import { useCameraOrientation } from '../composables/useCameraOrientation';
import vtkLPSView3DProxy from '../vtk/LPSView3DProxy';
import { useSceneBuilder } from '../composables/useSceneBuilder';
import { usePersistCameraConfig } from '../composables/usePersistCameraConfig';
import { useModelStore } from '../store/datasets-models';
import { LPSAxisDir } from '../types/lps';
import { useViewProxy } from '../composables/useViewProxy';
import { ViewProxyType } from '../core/proxies';
import { VolumeColorConfig } from '../store/view-configs/types';
import useVolumeColoringStore, {DEFAULT_AMBIENT,DEFAULT_DIFFUSE,DEFAULT_SPECULAR,
} from '../store/view-configs/volume-coloring';
import { getShiftedOpacityFromPreset } from '../utils/vtk-helpers';
import CropTool from './tools/crop/CropTool.vue';
import PanTool from './tools/PanTool.vue';
import { useWidgetManager } from '../composables/useWidgetManager';
import { VTKThreeViewWidgetManager } from '../constants';
import { useCropStore, croppingPlanesEqual } from '../store/tools/crop';
import { isViewAnimating } from '../composables/isViewAnimating';
import { ColoringConfig } from '../types/views';
import useViewCameraStore from '../store/view-configs/camera';
import { Maybe } from '../types';
import { useResetViewsEvents } from './tools/ResetViews.vue';
import Custom3DView from '@/src/components/Custom3DView.vue';function useCvrEffect(config: Ref<Maybe<VolumeColorConfig>>,imageRep: Ref<vtkVolumeRepresentationProxy | null>,viewProxy: Ref<vtkLPSView3DProxy>
) {const cvrParams = computed(() => config.value?.cvr);const repMapper = computedWithControl(imageRep,() => imageRep.value?.getMapper() as vtkVolumeMapper | undefined);const image = computedWithControl(imageRep,() => imageRep.value?.getInputDataSet() as vtkImageData | null | undefined);const volume = computedWithControl(imageRep,() => imageRep.value?.getVolumes()[0]);const renderer = computed(() => viewProxy.value.getRenderer());const isAnimating = isViewAnimating(viewProxy);const cvrEnabled = computed(() => {const enabled = !!cvrParams.value?.enabled;const animating = isAnimating.value;return enabled && !animating;});const requestRender = () => {if (!isAnimating.value) {viewProxy.value.renderLater();}};// lightsconst volumeCenter = computed(() => {if (!volume.value) return null;const volumeBounds = volume.value.getBounds();return [(volumeBounds[0] + volumeBounds[1]) / 2,(volumeBounds[2] + volumeBounds[3]) / 2,(volumeBounds[4] + volumeBounds[5]) / 2,] as Vector3;});const lightFollowsCamera = computed(() => cvrParams.value?.lightFollowsCamera ?? true);watch([volumeCenter, renderer, cvrEnabled, lightFollowsCamera],([center, ren, enabled, lightFollowsCamera_]) => {if (!center) return;if (ren.getLights().length === 0) {ren.createLight();}const light = ren.getLights()[0];if (enabled) {light.setFocalPoint(...center);light.setColor(1, 1, 1);light.setIntensity(1);light.setConeAngle(90);light.setPositional(true);ren.setTwoSidedLighting(false);if (lightFollowsCamera_) {light.setLightTypeToHeadLight();ren.updateLightsGeometryToFollowCamera();} else {light.setLightTypeToSceneLight();}} else {light.setPositional(false);}requestRender();},{ immediate: true });// sampling distanceconst volumeQuality = computed(() => cvrParams.value?.volumeQuality);watch([volume, image, repMapper, volumeQuality, cvrEnabled, isAnimating],([volume_, image_, mapper, volumeQuality_, enabled, animating]) => {if (!volume_ || !mapper || volumeQuality_ == null || !image_) return;if (animating) {mapper.setSampleDistance(0.75);mapper.setMaximumSamplesPerRay(1000);mapper.setGlobalIlluminationReach(0);mapper.setComputeNormalFromOpacity(false);} else {const dims = image_.getDimensions();const spacing = image_.getSpacing();const spatialDiagonal = vec3.length(vec3.fromValues(dims[0] * spacing[0],dims[1] * spacing[1],dims[2] * spacing[2]));// Use the average spacing for sampling by defaultlet sampleDistance = spacing.reduce((a, b) => a + b) / 3.0;// Adjust the volume sampling by the quality slider valuesampleDistance /= volumeQuality_ > 1 ? 0.5 * volumeQuality_ ** 2 : 1.0;const samplesPerRay = spatialDiagonal / sampleDistance + 1;mapper.setMaximumSamplesPerRay(samplesPerRay);mapper.setSampleDistance(sampleDistance);// Adjust the global illumination reach by volume quality slidermapper.setGlobalIlluminationReach(enabled ? 0.25 * volumeQuality_ : 0);mapper.setComputeNormalFromOpacity(!enabled && volumeQuality_ > 2);}requestRender();},{ immediate: true });// volume propertiesconst ambient = computed(() => cvrParams.value?.ambient ?? 0);const diffuse = computed(() => cvrParams.value?.diffuse ?? 0);const specular = computed(() => cvrParams.value?.specular ?? 0);watch([volume, image, ambient, diffuse, specular, cvrEnabled],([volume_, image_, ambient_, diffuse_, specular_, enabled]) => {if (!volume_ || !image_) return;const property = volume_.getProperty();property.setScalarOpacityUnitDistance(0,(0.5 * getDiagonalLength(image_.getBounds())) /Math.max(...image_.getDimensions()));property.setShade(true);property.setUseGradientOpacity(0, !enabled);property.setGradientOpacityMinimumValue(0, 0.0);const dataRange = image_.getPointData().getScalars().getRange();property.setGradientOpacityMaximumValue(0,(dataRange[1] - dataRange[0]) * 0.01);property.setGradientOpacityMinimumOpacity(0, 0.0);property.setGradientOpacityMaximumOpacity(0, 1.0);// do not toggle these parameters when animatingproperty.setAmbient(enabled ? ambient_ : DEFAULT_AMBIENT);property.setDiffuse(enabled ? diffuse_ : DEFAULT_DIFFUSE);property.setSpecular(enabled ? specular_ : DEFAULT_SPECULAR);requestRender();},{ immediate: true });// volumetric scattering blendingconst useVolumetricScatteringBlending = computed(() => cvrParams.value?.useVolumetricScatteringBlending ?? false);const volumetricScatteringBlending = computed(() => cvrParams.value?.volumetricScatteringBlending ?? 0);watch([useVolumetricScatteringBlending,volumetricScatteringBlending,repMapper,cvrEnabled,],([useVsb, vsb, mapper, enabled]) => {if (!mapper) return;if (enabled && useVsb) {mapper.setVolumetricScatteringBlending(vsb);} else {mapper.setVolumetricScatteringBlending(0);}requestRender();},{ immediate: true });// local ambient occlusionconst useLocalAmbientOcclusion = computed(() => cvrParams.value?.useLocalAmbientOcclusion ?? false);const laoKernelSize = computed(() => cvrParams.value?.laoKernelSize ?? 0);const laoKernelRadius = computed(() => cvrParams.value?.laoKernelRadius ?? 0);watch([useLocalAmbientOcclusion,laoKernelSize,laoKernelRadius,repMapper,cvrEnabled,],([useLao, kernelSize, kernelRadius, mapper, enabled]) => {if (!mapper) return;if (enabled && useLao) {mapper.setLocalAmbientOcclusion(true);mapper.setLAOKernelSize(kernelSize);mapper.setLAOKernelRadius(kernelRadius);} else {mapper.setLocalAmbientOcclusion(false);mapper.setLAOKernelSize(0);mapper.setLAOKernelRadius(0);}requestRender();},{ immediate: true });
}function useColoringEffect(config: Ref<Maybe<ColoringConfig>>,imageRep: Ref<vtkVolumeRepresentationProxy | null>,viewProxy: Ref<vtkLPSView3DProxy>
) {const colorBy = computed(() => config.value?.colorBy);const colorTransferFunction = computed(() => config.value?.transferFunction);const opacityFunction = computed(() => config.value?.opacityFunction);const proxyManager = useProxyManager();watch([imageRep, colorBy, colorTransferFunction, opacityFunction],([rep, colorBy_, colorFunc, opacityFunc]) => {if (!rep || !colorBy_ || !colorFunc || !opacityFunc || !proxyManager) {return;}const { arrayName, location } = colorBy_;const lut = proxyManager.getLookupTable(arrayName);lut.setMode(LookupTableProxyMode.Preset);lut.setPresetName(colorFunc.preset);lut.setDataRange(...colorFunc.mappingRange);const pwf = proxyManager.getPiecewiseFunction(arrayName);pwf.setMode(opacityFunc.mode);pwf.setDataRange(...opacityFunc.mappingRange);switch (opacityFunc.mode) {case vtkPiecewiseFunctionProxy.Mode.Gaussians:pwf.setGaussians(opacityFunc.gaussians);break;case vtkPiecewiseFunctionProxy.Mode.Points: {const opacityPoints = getShiftedOpacityFromPreset(opacityFunc.preset,opacityFunc.mappingRange,opacityFunc.shift,opacityFunc.shiftAlpha);if (opacityPoints) {pwf.setPoints(opacityPoints);}break;}case vtkPiecewiseFunctionProxy.Mode.Nodes:pwf.setNodes(opacityFunc.nodes);break;default:}if (rep) {// control color range manuallyrep.setRescaleOnColorBy(false);rep.setColorBy(arrayName, location);}// Need to trigger a render for when we are restoring from a state fileviewProxy.value.renderLater();},{ immediate: true });
}export default defineComponent({props: {id: {type: String,required: true,},viewDirection: {type: String as PropType<LPSAxisDir>,required: true,},viewUp: {type: String as PropType<LPSAxisDir>,required: true,},},components: {ViewOverlayGrid,CropTool,PanTool,Custom3DView,},setup(props) {const modelStore = useModelStore();const volumeColoringStore = useVolumeColoringStore();const viewCameraStore = useViewCameraStore();const { id: viewID, viewDirection, viewUp } = toRefs(props);const vtkContainerRef = ref<HTMLElement>();// --- computed vars --- //const {currentImageID: curImageID,currentImageMetadata: curImageMetadata,currentImageData,isImageLoading,} = useCurrentImage();// --- view proxy setup --- //const { viewProxy, setContainer: setViewProxyContainer } =useViewProxy<vtkLPSView3DProxy>(viewID, ViewProxyType.Volume);onMounted(() => {viewProxy.value.setOrientationAxesVisibility(true);viewProxy.value.setOrientationAxesType('cube');viewProxy.value.setBackground([0, 0, 0, 0]);setViewProxyContainer(vtkContainerRef.value);});onBeforeUnmount(() => {setViewProxyContainer(null);viewProxy.value.setContainer(null);});useResizeObserver(vtkContainerRef, () => viewProxy.value.resize());// --- scene setup --- //const { baseImageRep } = useSceneBuilder<vtkVolumeRepresentationProxy>(viewID,{baseImage: curImageID,models: computed(() => modelStore.idList),});// --- picking --- //// disables picking for crop control and morewatch(baseImageRep,(rep) => {if (rep) {rep.getVolumes().forEach((volume) => volume.setPickable(false));}},{ immediate: true });// --- widget manager --- //const { widgetManager } = useWidgetManager(viewProxy);provide(VTKThreeViewWidgetManager, widgetManager);// --- camera setup --- //const { cameraUpVec, cameraDirVec } = useCameraOrientation(viewDirection,viewUp,curImageMetadata);const resetCamera = () => {const bounds = curImageMetadata.value.worldBounds;const center = [(bounds[0] + bounds[1]) / 2,(bounds[2] + bounds[3]) / 2,(bounds[4] + bounds[5]) / 2,] as vec3;viewProxy.value.updateCamera(cameraDirVec.value,cameraUpVec.value,center);viewProxy.value.resetCamera();viewProxy.value.renderLater();};watch([baseImageRep, cameraDirVec, cameraUpVec],() => {const cameraConfig = viewCameraStore.getConfig(viewID.value,curImageID.value);// We don't want to reset the camera if we have a config we are restoringif (!cameraConfig) {// nextTick ensures resetCamera gets called after// useSceneBuilder refreshes the scene.nextTick(resetCamera);}},{immediate: true,});const { restoreCameraConfig } = usePersistCameraConfig(viewID,curImageID,viewProxy,'position','focalPoint','directionOfProjection','viewUp');watch(curImageID, () => {// See if we have a camera configuration to restoreconst cameraConfig = viewCameraStore.getConfig(viewID.value,curImageID.value);if (cameraConfig) {restoreCameraConfig(cameraConfig);viewProxy.value.getRenderer().resetCameraClippingRange();viewProxy.value.renderLater();}});// --- coloring setup --- //const volumeColorConfig = computed(() =>volumeColoringStore.getConfig(viewID.value, curImageID.value));watch([viewID, curImageID],() => {if (curImageID.value &&currentImageData.value &&!volumeColorConfig.value) {volumeColoringStore.resetToDefaultColoring(viewID.value,curImageID.value,currentImageData.value);}},{ immediate: true });// --- CVR parameters --- //useCvrEffect(volumeColorConfig, baseImageRep, viewProxy);// --- coloring --- //useColoringEffect(volumeColorConfig, baseImageRep, viewProxy);// --- cropping planes --- //const cropStore = useCropStore();const croppingPlanes = cropStore.getComputedVTKPlanes(curImageID);watch(croppingPlanes,(planes, oldPlanes) => {const mapper = baseImageRep.value?.getMapper();if (!mapper ||!planes ||(oldPlanes && croppingPlanesEqual(planes, oldPlanes)))return;mapper.removeAllClippingPlanes();planes.forEach((plane) => mapper.addClippingPlane(plane));mapper.modified();viewProxy.value.renderLater();},{ immediate: true });// --- Listen to ResetViews event --- //const events = useResetViewsEvents();events.onClick(() => resetCamera());// --- template vars --- //return {vtkContainerRef,viewID,active: false,topLeftLabel: computed(() =>volumeColorConfig.value?.transferFunction.preset.replace(/-/g, ' ') ??''),isImageLoading,resetCamera,};},
});
</script><style scoped>
.model-container {width: 100%;height: 600px;position: relative;
}
</style><style scoped src="@/src/components/styles/vtk-view.css"></style>
<style scoped src="@/src/components/styles/utils.css"></style><style scoped>
.vtk-three-container {background-color: black;grid-template-columns: auto;
}
</style>

2.新增Custom3DView.vue

在这里插入图片描述

src/components目录下新增Custom3DView.vue。用来显示后端Python生成的stl。

全部代码如下:

<template><div ref="container" class="model-container"></div>
</template><script>
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader';
import { toRaw } from 'vue';export default {data() {return {loadingProgress: 0,loadError: null,animateId: null};},mounted() {this.initThreeContext();this.loadSTLModel();this.setupAnimation();},beforeDestroy() {this.cleanupResources();},methods: {initThreeContext() {const container = this.$refs.container;// 场景配置this._scene = new THREE.Scene();this._scene.background = new THREE.Color(0x000000);// 相机配置this._camera = new THREE.PerspectiveCamera(45, // 缩小视角增加近景效果container.clientWidth / container.clientHeight,0.1,500 // 缩小可视范围提升渲染性能); this._camera.position.set(30, 30, 30); // 初始位置更靠近模型// 渲染器配置(网页7的黑色背景方案)this._renderer = new THREE.WebGLRenderer({ antialias: true,alpha: true // 保留alpha通道以备后续扩展});this._renderer.setClearColor(0x000000, 1); // 双重确保背景颜色this._renderer.setSize(container.clientWidth, container.clientHeight);container.appendChild(this._renderer.domElement);// 光源优化const ambientLight = new THREE.AmbientLight(0x404040);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(15, 15, 15);this._scene.add(ambientLight, directionalLight);// 控制器配置this._controls = new OrbitControls(toRaw(this._camera), this._renderer.domElement);this._controls.enableDamping = true;this._controls.dampingFactor = 0.05;},loadSTLModel() {const objSTLLoader=new STLLoader()objSTLLoader.crossOrigin='Anonymous'objSTLLoader.load( 'https://stl所在路径.stl', geometry => {// 添加模型前清空旧模型this.clearExistingModel();// 材质配置(浅灰色方案)const material  = new THREE.MeshPhongMaterial({color: 0xcccccc, // 浅灰色specular: 0x222222, shininess: 150, side: THREE.DoubleSide});const mesh = new THREE.Mesh(geometry, material);geometry.center();mesh.scale.set(0.1, 0.1, 0.1);// 自动聚焦模型const box = new THREE.Box3().setFromObject(mesh);const center = box.getCenter(new THREE.Vector3());toRaw(this._camera).lookAt(center);toRaw(this._scene).add(mesh); },progress => {this.loadingProgress = (progress.loaded / progress.total) * 100},error => {this.loadError = '模型加载失败,请检查网络或文件路径'});},setupAnimation() {const animate = () => {this.animateId = requestAnimationFrame(animate);toRaw(this._controls).update();this._renderer.render(toRaw(this._scene), toRaw(this._camera));};animate();},cleanupResources() {cancelAnimationFrame(this.animateId);toRaw(this._controls).dispose();this._renderer.dispose();toRaw(this._scene).traverse(obj => {if (obj.isMesh) {obj.geometry.dispose();obj.material.dispose();}});}}
};
</script><style scoped>
.model-container {width: 100%;height: 600px;position: relative;background: #000; /* 备用黑色背景 */
}
</style>

3.Python生成stl三维文件

在服务端用Python生成stl:

from pydicom import dcmread
import pylibjpegimport numpy as np
import pydicom
import pydicom.pixel_data_handlers.gdcm_handler as gdcm_handlerimport os
import matplotlib.pyplot as plt
from glob import glob
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import scipy.ndimage
from skimage import measure
from mpl_toolkits import mplot3d
from stl import mesh
import trimesh
pydicom.config.image_handlers = [None, gdcm_handler]
pydicom.config.image_handlers = ['gdcm_handler']def load_scan(path):slices = []# count = 0for s in os.listdir(path):ds = pydicom.dcmread(path + '/' + s, force=True)ds.PhotometricInterpretation = 'YBR_FULL'if s != '.DS_Store':  # This is for AttributeError: 'FileDataset' object has no attribute 'InstanceNumber'slices.append(ds)slices.sort(key=lambda x: int(x.InstanceNumber))try:slice_thickness = np.abs(slices[0].ImagePositionPatient[2] - slices[1].ImagePositionPatient[2])except:slice_thickness = np.abs(slices[0].SliceLocation - slices[1].SliceLocation)for s in slices:s.SliceThickness = slice_thicknessreturn slicesdef get_pixels_hu(scans):image = np.stack([s.pixel_array for s in scans])image = image.astype(np.int16)image[image == -2000] = 0# Convert to Hounsfield units (HU)intercept = scans[0].RescaleInterceptslope = scans[0].RescaleSlopeif slope != 1:image = slope * image.astype(np.float64)image = image.astype(np.int16)image += np.int16(intercept)return np.array(image, dtype=np.int16)def make_mesh(image, threshold=-300, step_size=1):print("Transposing surface")p = image.transpose(2, 1, 0)print("Calculating surface")verts, faces, norm, val = measure.marching_cubes(p, threshold, step_size=step_size, allow_degenerate=True)return verts, facesdef resample(image, scan, new_spacing=[1, 1, 1]):# Determine current pixel spacing, change this function to get better resultspacing = [float(scan[0].SliceThickness)] + [float(i) for i in scan[0].PixelSpacing]spacing = np.array(spacing)resize_factor = [spacing[0] / new_spacing[0], spacing[1] / new_spacing[1], spacing[2] / new_spacing[2]]new_real_shape = np.multiply(image.shape, resize_factor)new_shape = np.round(new_real_shape)real_resize_factor = new_shape / image.shapenew_spacing = spacing / real_resize_factorimage = scipy.ndimage.interpolation.zoom(image, real_resize_factor)return image, new_spacingif __name__ == "__main__":from matplotlib.cm import get_cmapimport matplotlib.colors as mcolorsdata_path = "/mnt/data_18T/data/口腔/CBCT及三维重建/dicom"output_path = "/mnt/data_18T/data/口腔/CBCT及三维重建/stl_path/"if not os.path.exists(output_path):  # create the output pathos.mkdir(output_path)patient = load_scan(data_path)images = get_pixels_hu(patient)imgs_after_resamp, spacing = resample(images.astype(np.float64), patient, [1, 0.5, 1])v, f = make_mesh(imgs_after_resamp, 350, 1)# save the stl filevertices = vfaces = f# 创建颜色列表colors = get_cmap('Greens')(np.linspace(0, 1, len(vertices)))colors = mcolors.to_rgba_array(colors)mesh = trimesh.Trimesh(vertices=vertices, faces=faces)mesh.export(output_path + 'cube2.stl', file_type="stl")

4.最终效果

在这里插入图片描述
在这里插入图片描述
注:我的是集显,配置不算高,在使用stl显示三维的情况下,很流畅。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/74342.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

debug - 安装.msi时,为所有用户安装程序

文章目录 debug - 安装.msi时&#xff0c;为所有用户安装程序概述笔记试试在目标.msi后面直接加参数的测试 备注备注END debug - 安装.msi时&#xff0c;为所有用户安装程序 概述 为了测试&#xff0c;装了一个test.msi. 安装时&#xff0c;只有安装路径的选择&#xff0c;没…

Java Stream两种list判断字符串是否存在方案

这里写自定义目录标题 背景初始化方法一、filter过滤方法二、anyMatch匹配 背景 在项目开发中&#xff0c;经常遇到筛选list中是否包含某个子字符串&#xff0c;有多种方式&#xff0c;本篇主要介绍stream流的filter和anyMatch两种方案&#xff0c;记录下来&#xff0c;方便备…

3. 轴指令(omron 机器自动化控制器)——>MC_SetOverride

机器自动化控制器——第三章 轴指令 12 MC_SetOverride变量▶输入变量▶输出变量▶输入输出变量 功能说明▶时序图▶重启运动指令▶多重启动运动指令▶异常 MC_SetOverride 变更轴的目标速度。 指令名称FB/FUN图形表现ST表现MC_SetOverride超调值设定FBMC_SetOverride_instan…

VLAN章节学习

为什么会有vlan这个技术&#xff1f; 1.通过划分广播域来降低广播风暴导致的设备性能下降&#xff1b; 2.提高网络管理的灵活性和通过隔离网络带来的安全性&#xff1b; 3.在成本不变的情况下增加更多的功能性&#xff1b; VLAN又称虚拟局域网&#xff08;再此扩展&#xf…

FPGA时钟约束

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、Create_clock 前言 时钟周期约束&#xff0c;就是对时钟进行约束。 一、Create_clock create_clock -name <name> -period <period> -waveform …

spring-security原理与应用系列:建造者

目录 1.构建过程 AbstractSecurityBuilder AbstractConfiguredSecurityBuilder WebSecurity 2.建造者类图 SecurityBuilder ​​​​​​​AbstractSecurityBuilder ​​​​​​​AbstractConfiguredSecurityBuilder ​​​​​​​WebSecurity 3.小结 紧接上一篇文…

OpenHarmony子系统开发 - 电池管理(二)

OpenHarmony子系统开发 - 电池管理&#xff08;二&#xff09; 五、充电限流限压定制开发指导 概述 简介 OpenHarmony默认提供了充电限流限压的特性。在对终端设备进行充电时&#xff0c;由于环境影响&#xff0c;可能会导致电池温度过高&#xff0c;因此需要对充电电流或电…

xy轴不等比缩放问题——AUTOCAD c#二次开发

在 AutoCAD .net api里&#xff0c;部分实体&#xff0c;像文字、属性、插入块等&#xff0c;是不支持非等比缩放的。 如需对AutoCAD中图形进行xyz方向不等比缩放&#xff0c;则需进行额外的函数封装。 选择图元&#xff0c;指定缩放基准点&#xff0c;scaleX 0.5, scaleY …

如何在 HTML 中创建一个有序列表和无序列表,它们的语义有何不同?

大白话如何在 HTML 中创建一个有序列表和无序列表&#xff0c;它们的语义有何不同&#xff1f; 1. HTML 中有序列表和无序列表的基本概念 在 HTML 里&#xff0c;列表是一种用来组织信息的方式。有序列表就是带有编号的列表&#xff0c;它可以让内容按照一定的顺序呈现&#…

Postman最新详细安装及使用教程【附安装包】

一、Postman介绍 ‌Postman是一个功能强大的API测试工具&#xff0c;主要用于模拟和测试各种HTTP请求&#xff0c;支持GET、POST、PUT、DELETE等多种请求方法。‌通过Postman&#xff0c;用户可以发送请求并查看返回的响应&#xff0c;检查响应的内容和状态&#xff0c;从而验…

整合vue+Element UI 开发管理系统

1、 安装 Node.js 和 npm 确保安装了 Node.js 和 npm。可以通过 Node.js 官网 下载。 2、 创建 Vue 项目 安装cli npm install -g vue/cli 使用 Vue CLI 创建一个新的 Vue 项目。 vue create admin-system cd admin-system npm run serve 出现这个页面表示vue创建成功 安…

3. 轴指令(omron 机器自动化控制器)——>MC_Stop

机器自动化控制器——第三章 轴指令 9 MC_Stop变量▶输入变量▶输出变量▶输入输出变量 功能说明▶指令详情▶时序图▶重启运动指令▶多重启动运动指令▶异常 MC_Stop 使轴减速停止。 指令名称FB/FUN图形表现ST表现MC_Stop强制停止FBMC_Stop_instance (Axis :《参数》 ,Execu…

C#中修饰符——abstract、virtual

一、多态简介 在面向对象编程的过程中&#xff0c;多态体现出来的是【一个接口&#xff0c;多个功能】&#xff1b;多态性体现在2个方面&#xff1a; 1、程序运行时&#xff0c;在方法参数、集合或数组等位置&#xff0c;派生类对象可以作为基类的对象处理&#xff1b;这样该对…

Spring Boot + Spring Integration整合MQTT打造双向通信客户端

1. 概述 本文分两个章节讲解MQTT相关的知识&#xff0c;第一部份主要讲解MQTT的原理和相关配置&#xff0c;第二个章节主要讲和Spring boot的integration相结合代码的具体实现&#xff0c;如果想快速实现功能&#xff0c;可直接跳过第一章节查看第二章讲。 1.1 MQTT搭建 为了…

2025前端面试题记录

vue项目目录的执行顺序是怎么样的&#xff1f; 1、package.json   在执行npm run dev时&#xff0c;会在当前目录寻找package.json文件&#xff0c;此文件包含了项目的名称版本、项目依赖等相关信息。 2、webpack.config.js(会被vue-cli脚手架隐藏) 3、vue.config.js   对…

专题|Python贝叶斯网络BN动态推理因果建模:MLE/Bayes、有向无环图DAG可视化分析呼吸疾病、汽车效能数据2实例合集

原文链接&#xff1a;https://tecdat.cn/?p41199 作为数据科学家&#xff0c;我们始终在探索能够有效处理复杂系统不确定性的建模工具。本专题合集系统性地解构了贝叶斯网络&#xff08;BN&#xff09;这一概率图模型在当代数据分析中的创新应用&#xff0c;通过开源工具bnlea…

WX小程序

下载 package com.sky.utils;import com.alibaba.fastjson.JSONObject; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.Cl…

Vulnhub-wordpress通关攻略

姿势一、后台修改模板拿WebShell 第一步&#xff1a;进⼊Vulhub靶场并执⾏以下命令开启靶场&#xff1b;在浏览器中访问并安装好.... 第二步&#xff1a;找到外观--编辑--404.php&#xff0c;将原内容删除并修改为一句话木马&#xff0c;点击更新--File edited successfully. &…

开源链动2+1模式、AI智能名片与S2B2C商城小程序源码在社交电商渠道拓宽中的协同应用研究

摘要&#xff1a;本文基于"开源链动21模式""AI智能名片""S2B2C商城小程序源码"三大技术要素&#xff0c;探讨社交电商时代商家渠道拓宽的创新路径。通过解析各技术的核心机制与应用场景&#xff0c;结合京东便利店等实际案例&#xff0c;论证其对…

【蓝桥杯速成】| 10.回溯切割

前面两篇内容我们都是在做有关回溯问题的组合应用 今天的题目主题是&#xff1a;回溯法在切割问题的应用 题目一&#xff1a;分割回文串 问题描述 131. 分割回文串 - 力扣&#xff08;LeetCode&#xff09; 给你一个字符串 s&#xff0c;请你将 s 分割成一些 子串&#xff…