Ver código fonte

修复播放多帧失败的问题,目前可以正常播放多帧图了

dengdx 1 semana atrás
pai
commit
6843994739

+ 39 - 37
src/pages/view/components/playback/PlaybackController.tsx

@@ -54,7 +54,7 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
   const totalFrames = useSelector(selectTotalFrames(viewportId));
   const playbackSpeed = useSelector(selectPlaybackSpeed(viewportId));
   const isLoop = useSelector(selectIsLoop(viewportId));
-  
+
   // 保存viewport和renderingEngine引用
   const viewportRef = useRef<Types.IStackViewport | null>(null);
   const renderingEngineRef = useRef<Types.IRenderingEngine | null>(null);
@@ -63,27 +63,29 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
 
   /**
    * 获取viewport实例
+   * 每次都重新获取,避免使用已销毁的viewport缓存引用
    */
   const getViewport = useCallback((): Types.IStackViewport | null => {
     try {
-      if (!viewportRef.current || !renderingEngineRef.current) {
-        const renderingEngine = cornerstone.getRenderingEngine(renderingEngineId);
-        if (!renderingEngine) {
-          console.warn(`[PlaybackController] Rendering engine not found: ${renderingEngineId}`);
-          return null;
-        }
-        
-        const viewport = renderingEngine.getViewport(viewportId) as Types.IStackViewport;
-        if (!viewport) {
-          console.warn(`[PlaybackController] Viewport not found: ${viewportId}`);
-          return null;
-        }
-        
-        viewportRef.current = viewport;
-        renderingEngineRef.current = renderingEngine;
+      // 总是从renderingEngine重新获取最新的viewport
+      // 不使用缓存,避免viewport被销毁后仍使用旧引用的问题
+      const renderingEngine = cornerstone.getRenderingEngine(renderingEngineId);
+      if (!renderingEngine) {
+        console.warn(`[PlaybackController] Rendering engine not found: ${renderingEngineId}`);
+        return null;
+      }
+
+      const viewport = renderingEngine.getViewport(viewportId) as Types.IStackViewport;
+      if (!viewport) {
+        console.warn(`[PlaybackController] Viewport not found: ${viewportId}`);
+        return null;
       }
-      
-      return viewportRef.current;
+
+      // 更新缓存引用(供调试用)
+      viewportRef.current = viewport;
+      renderingEngineRef.current = renderingEngine;
+
+      return viewport;
     } catch (error) {
       console.error(`[PlaybackController] Error getting viewport:`, error);
       onPlaybackError?.(error as Error);
@@ -101,19 +103,19 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
     try {
       // 确保帧索引在有效范围内
       const clampedFrame = Math.max(0, Math.min(frameIndex, totalFrames - 1));
-      
+
       // 如果是多帧DICOM,使用setImageIdIndex
       if (totalFrames > 1) {
         await viewport.setImageIdIndex(clampedFrame);
         viewport.render();
       }
-      
+
       // 更新Redux状态
       if (clampedFrame !== currentFrame) {
         dispatch(setCurrentFrame({ viewportId, frame: clampedFrame }));
         onFrameChange?.(clampedFrame);
       }
-      
+
       lastFrameRef.current = clampedFrame;
     } catch (error) {
       console.error(`[PlaybackController] Error switching to frame ${frameIndex}:`, error);
@@ -130,17 +132,17 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
     }
 
     const intervalMs = 1000 / playbackSpeed;
-    
+
     const playIntervalId = setInterval(() => {
       dispatch(nextFrame(viewportId));
     }, intervalMs);
 
     playIntervalRef.current = playIntervalId;
-    
+
     // 保存定时器ID到Redux(用于清理)
-    dispatch(setPlayIntervalId({ 
-      viewportId, 
-      intervalId: playIntervalId as any 
+    dispatch(setPlayIntervalId({
+      viewportId,
+      intervalId: playIntervalId as any
     }));
 
     console.log(`[PlaybackController] Started playback timer for ${viewportId}, speed: ${playbackSpeed}fps`);
@@ -226,7 +228,7 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
     // 重置viewport引用,强制重新获取
     viewportRef.current = null;
     renderingEngineRef.current = null;
-    
+
     // 预先获取viewport以验证可用性
     getViewport();
   }, [viewportId, renderingEngineId, getViewport]);
@@ -242,7 +244,7 @@ export const PlaybackController: React.FC<PlaybackControllerProps> = ({
 export const usePlaybackController = (viewportId: string) => {
   const dispatch = useDispatch();
   const playbackState = useSelector(selectViewportPlayback(viewportId));
-  
+
   return {
     // 状态
     playbackState,
@@ -250,31 +252,31 @@ export const usePlaybackController = (viewportId: string) => {
     currentFrame: playbackState?.currentFrame || 0,
     totalFrames: playbackState?.totalFrames || 1,
     isMultiFrame: playbackState?.isMultiFrame || false,
-    
+
     // 控制方法
     togglePlayback: () => {
-      dispatch(setPlaybackState({ 
-        viewportId, 
-        isPlaying: !playbackState?.isPlaying 
+      dispatch(setPlaybackState({
+        viewportId,
+        isPlaying: !playbackState?.isPlaying
       }));
     },
-    
+
     play: () => {
       dispatch(setPlaybackState({ viewportId, isPlaying: true }));
     },
-    
+
     pause: () => {
       dispatch(setPlaybackState({ viewportId, isPlaying: false }));
     },
-    
+
     goToFrame: (frame: number) => {
       dispatch(setCurrentFrame({ viewportId, frame }));
     },
-    
+
     nextFrame: () => {
       dispatch(nextFrame(viewportId));
     },
-    
+
     previousFrame: () => {
       const prevFrame = Math.max(0, (playbackState?.currentFrame || 0) - 1);
       dispatch(setCurrentFrame({ viewportId, frame: prevFrame }));

+ 41 - 2
src/pages/view/components/viewers/stack.image.viewer.tsx

@@ -1571,8 +1571,47 @@ const StackViewer = ({
         // 3. 预检查图像可访问性(可选)
         // await validateImageUrls(imageUrls);
 
-        // 4. 设置图像栈,包含超时保护
-        await safeSetStack(viewport, imageUrls, imageIndex);
+        // 4. 多帧DICOM预处理:检测并扩展imageIds
+        let finalImageUrls = imageUrls;
+        let detectedFrameCount = 1;
+
+        // 只有当imageUrls只有一个元素时,才可能是多帧DICOM需要expansion
+        if (imageUrls.length === 1) {
+          try {
+            // 预先分析DICOM元数据以检测帧数
+            const { DicomMetadataAnalyzer } = await import('@/utils/dicom/DicomMetadataAnalyzer');
+            const quickAnalysis = await DicomMetadataAnalyzer.analyze(imageUrls[0], undefined, {
+              baseUrl: IP_PORT
+            });
+
+            detectedFrameCount = quickAnalysis.frameCount;
+
+            // 如果检测到多帧,扩展imageIds数组
+            if (detectedFrameCount > 1) {
+              console.log(`[StackViewer] 检测到多帧DICOM (${detectedFrameCount}帧),扩展imageIds...`);
+
+              const baseImageId = imageUrls[0];
+              const expandedImageIds: string[] = [];
+
+              for (let i = 0; i < detectedFrameCount; i++) {
+                // 为每一帧创建对应的imageId
+                expandedImageIds.push(`${baseImageId}${baseImageId.includes('?') ? '&' : '?'}frame=${i}`);
+              }
+
+              finalImageUrls = expandedImageIds;
+              console.log(`[StackViewer] ImageIds已扩展:`, {
+                原始: 1,
+                扩展后: expandedImageIds.length,
+                示例: expandedImageIds.slice(0, Math.min(3, expandedImageIds.length))
+              });
+            }
+          } catch (error) {
+            console.warn(`[StackViewer] 多帧预检测失败,使用原始imageUrls:`, error);
+          }
+        }
+
+        // 5. 设置图像栈,使用扩展后的imageUrls
+        await safeSetStack(viewport, finalImageUrls, imageIndex);
 
         // 5. 初始化播放状态(用于多帧播放控制)
         // 使用 DicomMetadataAnalyzer 进行深度分析,从 DICOM 元数据读取帧数