import React, { useEffect, useCallback, useRef } from 'react'; import { Layout, Button, Typography, Select, message, Spin, Alert } from 'antd'; import { ArrowLeftOutlined } from '@ant-design/icons'; import { useDispatch } from 'react-redux'; import { useAppSelector } from '@/states/store'; import { useImageEnhancementSDK } from '@/hooks/useImageEnhancementSDK'; import { selectProcessingMode } from '@/states/system/processingModeSlice'; import { IMAGE_LOADER_PREFIX } from '@/lib/image-enhancement-sdk'; import { switchToAdvancedProcessingPanel } from '../../../states/panelSwitchSliceForView'; import { loadImageProcessingParams, saveProcessingParams, updateParameter, applyPreset, resetToPreset, setAlgorithm, setLUT, selectParameters, selectSelectedStyle, selectSelectedAlgorithm, selectSelectedLUT, selectIsLoading, selectIsSaving, selectIsInitialLoad, selectError, selectCurrentImageId, } from '../../../states/view/sliderAdjustmentPanelSlice'; import { buildProcessedDcmUrl } from '../../../API/imageActions'; import { updateViewerUrl } from '../../../states/view/viewerContainerSlice'; import { PARAMETER_RANGES, ALGORITHM_OPTIONS } from '../../../domain/processingPresets'; import { LUT_OPTIONS } from '../../../domain/lutConfig'; import type { ProcessingStyle, LUTType, FullProcessingParams } from '../../../types/imageProcessing'; import ParameterSlider from './ParameterSlider'; import store from '@/states/store'; import { getSopInstanceUidFromUrl } from '@/API/bodyPosition'; const { Header, Content } = Layout; const { Title } = Typography; const { Option } = Select; /** * 滑动参数调节面板 * 三级面板,用于调整图像处理参数 */ const SliderAdjustmentPanel = () => { const dispatch = useDispatch(); // 从 Redux 获取状态 const parameters = useAppSelector(selectParameters); const selectedStyle = useAppSelector(selectSelectedStyle); const selectedAlgorithm = useAppSelector(selectSelectedAlgorithm); const selectedLUT = useAppSelector(selectSelectedLUT); const isLoading = useAppSelector(selectIsLoading); const isSaving = useAppSelector(selectIsSaving); const isInitialLoad = useAppSelector(selectIsInitialLoad); const error = useAppSelector(selectError); const currentImageId = useAppSelector(selectCurrentImageId); // 获取处理模式配置 const processingMode = useAppSelector(selectProcessingMode); const isWASMMode = processingMode === 'wasm'; // 初始化WASM SDK(仅在WASM模式下) const { sdk, isSDKReady, isInitializing, error: sdkError } = useImageEnhancementSDK({ sopInstanceUid: currentImageId || '', enabled: isWASMMode, }); // 防抖定时器 const saveTimerRef = useRef(null); // 组件挂载时加载参数 useEffect(() => { // 从当前选中的图像获取 sopInstanceUid // const dcmUrls = store.getState().viewerContainer.selectedViewers; //正常情况下只会得到一个选中的图像,否则进入不了滑杆调参页面 if (dcmUrls.length !== 1) { console.error('没有选中的图像或者数量大于1,无法加载参数,异常现象'); return; } const sopInstanceUid = getSopInstanceUidFromUrl(dcmUrls[0]); if (sopInstanceUid) { dispatch(loadImageProcessingParams(sopInstanceUid) as any); } }, [dispatch]); // 监听错误 useEffect(() => { if (error) { message.error(error); } }, [error]); /** * 返回上级面板 */ const handleReturn = () => { dispatch(switchToAdvancedProcessingPanel()); }; /** * 传统模式:防抖更新预览图像URL */ const debouncedPreview = useCallback( (params: FullProcessingParams) => { // 如果是初始加载,不触发预览 if (isInitialLoad) { return; } // 清除之前的定时器 if (saveTimerRef.current) { clearTimeout(saveTimerRef.current); } // 设置新的定时器 saveTimerRef.current = setTimeout(() => { if (currentImageId) { try { // 获取原始URL const dcmUrls = store.getState().viewerContainer.selectedViewers; if (dcmUrls.length !== 1) { console.error('没有选中的图像或者数量大于1,无法更新预览'); return; } const originalUrl = dcmUrls[0]; // 构建处理后的dcm URL(带参数) const processedUrl = buildProcessedDcmUrl(currentImageId, { contrast: params.contrast.toString(), detail: params.detail.toString(), latitude: params.latitude.toString(), noise: params.noise.toString(), }); // 更新viewer URL以触发重新渲染 dispatch(updateViewerUrl({ originalUrl, newUrl: `dicomweb:${processedUrl}`, })); console.log('✅ [传统模式] 已更新预览图像URL:', processedUrl); console.log('参数:', params); } catch (error) { console.error('更新预览图像失败:', error); message.error('更新预览图像失败'); } } }, 500); // 500ms 防抖延迟 }, [currentImageId, isInitialLoad, dispatch] ); /** * WASM模式:防抖更新预览 */ const debouncedWASMPreview = useCallback( (params: FullProcessingParams) => { if (isInitialLoad || !sdk || !currentImageId) { return; } // 清除之前的定时器 if (saveTimerRef.current) { clearTimeout(saveTimerRef.current); } // 设置新的定时器(WASM模式更短的防抖时间) saveTimerRef.current = setTimeout(async () => { try { console.log('🔧 [WASM模式] 开始应用参数...'); // 1. 更新SDK参数管理器 sdk.parameterManager.updateParameters(params); // 2. 生成唯一imageId(带时间戳强制重新加载) const timestamp = Date.now(); const enhancedImageId = `${IMAGE_LOADER_PREFIX}${currentImageId}_${timestamp}`; // 3. 获取原始URL const dcmUrls = store.getState().viewerContainer.selectedViewers; if (dcmUrls.length !== 1) { console.error('没有选中的图像或者数量大于1'); return; } const originalUrl = dcmUrls[0]; // 4. 更新viewer URL以触发重新加载 dispatch(updateViewerUrl({ originalUrl, newUrl: enhancedImageId, })); console.log('✅ [WASM模式] WASM增强完成,图像已重新加载'); console.log('参数:', params); } catch (error) { console.error('❌ [WASM模式] 参数应用失败:', error); message.error('WASM参数应用失败'); } }, 300); // WASM模式使用更短的防抖时间 }, [currentImageId, isInitialLoad, sdk, dispatch] ); /** * 参数变化处理(双模式支持) */ const handleParameterChange = (name: keyof FullProcessingParams, value: number) => { dispatch(updateParameter({ name, value })); // 触发防抖预览(仅后端参数触发预览) const newParams = { ...parameters, [name]: value }; // 只有后端参数(contrast, detail, latitude, noise)才触发预览 if (['contrast', 'detail', 'latitude', 'noise', 'brightness', 'sharpness'].includes(name)) { // 根据处理模式选择预览函数 if (isWASMMode) { debouncedWASMPreview(newParams); } else { debouncedPreview(newParams); } } }; /** * 风格变化处理 */ const handleStyleChange = (value: ProcessingStyle) => { dispatch(applyPreset(value)); // 风格切换后立即保存 if (currentImageId) { const preset = parameters; // 应用风格后的参数会在 Redux 中更新 setTimeout(() => { dispatch( saveProcessingParams({ sopInstanceUid: currentImageId, params: { contrast: preset.contrast, detail: preset.detail, latitude: preset.latitude, noise: preset.noise, ww_coef: preset.brightness, wl_coef: preset.sharpness, }, }) as any ); }, 100); } }; /** * 算法变化处理 */ const handleAlgorithmChange = (value: string) => { dispatch(setAlgorithm(value)); }; /** * LUT 变化处理 */ const handleLUTChange = (value: LUTType) => { dispatch(setLUT(value)); }; /** * 重置参数 */ const handleReset = () => { dispatch(resetToPreset()); message.success('已重置为当前风格的默认参数'); // 重置后保存 if (currentImageId) { setTimeout(() => { dispatch( saveProcessingParams({ sopInstanceUid: currentImageId, params: { contrast: parameters.contrast, detail: parameters.detail, latitude: parameters.latitude, noise: parameters.noise, ww_coef: parameters.brightness, wl_coef: parameters.sharpness, }, }) as any ); }, 100); } }; /** * 手动保存参数 */ const handleSave = () => { if (currentImageId) { dispatch( saveProcessingParams({ sopInstanceUid: currentImageId, params: { contrast: parameters.contrast, detail: parameters.detail, latitude: parameters.latitude, noise: parameters.noise, ww_coef: parameters.brightness, wl_coef: parameters.sharpness, }, }) as any ).then(() => { message.success('参数保存成功'); }); } }; // 清理定时器 useEffect(() => { return () => { if (saveTimerRef.current) { clearTimeout(saveTimerRef.current); } }; }, []); return ( {/* 顶部导航栏 */}
{/* 主体内容 */} {/* 处理模式状态提示 */} {isWASMMode && ( )} {!isWASMMode && ( )} {isLoading && (
)}
{/* 增益滑块 */} handleParameterChange('contrast', value)} disabled={isLoading} /> {/* 细节滑块 */} handleParameterChange('detail', value)} disabled={isLoading} /> {/* 动态范围滑块 */} handleParameterChange('latitude', value)} disabled={isLoading} /> {/* 噪声模式滑块 */} handleParameterChange('noise', value)} disabled={isLoading} /> {/* 对比度滑块(前端维护) */} handleParameterChange('brightness', value)} disabled={isLoading} /> {/* 亮度滑块(前端维护) */} handleParameterChange('sharpness', value)} disabled={isLoading} /> {/* 算法选择 */}
算法
{/* LUT 选择 */}
LUT
{/* 风格选择 */}
风格
{/* 底部按钮 */}
); }; export default SliderAdjustmentPanel;