123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- import React, { useEffect } from 'react';
- import StackViewer, {
- activateMagnifier,
- addLMark,
- addMask,
- addRLabel,
- adjustBrightnessAndContrast,
- ApplyColormap,
- deleteSelectedMark,
- fitImageSize,
- HorizontalFlip,
- invertContrast,
- InvertImage,
- remoteMask,
- ResetImage,
- rotateAnyAngle,
- RotateClockwise90,
- RotateCounterclockwise90,
- setOriginalSize,
- VerticalFlip,
- activateLengthMeasurement,
- deactivateLengthMeasurement,
- clearLengthMeasurements,
- activateAngleMeasurement,
- deactivateAngleMeasurement,
- clearAngleMeasurements,
- } from './viewers/stack.image.viewer';
- import { useSelector, useDispatch } from 'react-redux';
- import store, { RootState } from '@/states/store';
- import { clearAction } from '@/states/view/functionAreaSlice';
- import {
- selectGridLayout,
- selectSelectedViewers,
- selectAllViewerUrls,
- setGridLayout as setGridLayoutAction,
- setAllViewers,
- toggleViewerSelection,
- } from '@/states/view/viewerContainerSlice';
- import {
- clearMeasurementAction,
- selectCurrentMeasurementAction,
- setToolActive,
- setToolInactive,
- } from '@/states/view/measurementPanelSlice';
- import * as cornerstone from '@cornerstonejs/core';
- import * as cornerstoneTools from '@cornerstonejs/tools';
- import { SystemMode } from '@/states/systemModeSlice';
- import * as cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
- import { MeasurementToolManager } from '@/utils/measurementToolManager';
- const renderingEngineId = 'myRenderingEngine';
- const setup = () => {
- // 初始化 Cornerstone
- cornerstone.init();
- cornerstoneTools.init();
- const state = store.getState();
- console.log(`当前系统模式:${state.systemMode.mode}`);
- const token =
- state.systemMode.mode === SystemMode.Emergency
- ? state.product.guest
- : state.userInfo.token;
- console.log(`token stack.image.viewer: ${token}`);
- cornerstoneDICOMImageLoader.init({
- maxWebWorkers: navigator.hardwareConcurrency || 1,
- errorInterceptor: (error) => {
- if (error.status === 401) {
- console.error('Authentication failed. Please refresh the token.');
- }
- console.error(`请求dcm文件出错:${error}`);
- },
- beforeSend: (xhr, imageId, defaultHeaders) => {
- return {
- ...defaultHeaders,
- Authorization: `Bearer ${token}`,
- Language: 'en',
- Product: 'DROS',
- Source: 'Electron',
- };
- },
- });
- // 创建渲染引擎
- new cornerstone.RenderingEngine(renderingEngineId);
- };
- setup();
- interface ViewerContainerProps {
- imageUrls: string[];
- }
- const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
- console.log(`[ViewerContainer] 新渲染 imageUrls:`, imageUrls);
-
- // 从 Redux 获取状态
- const gridLayout = useSelector(selectGridLayout);
- const selectedViewerUrls = useSelector(selectSelectedViewers);
- const allViewerUrls = useSelector(selectAllViewerUrls);
- const action = useSelector((state: RootState) => state.functionArea.action);
- const measurementAction = useSelector(selectCurrentMeasurementAction);
- const dispatch = useDispatch();
- const selectedBodyPosition = useSelector(
- (state: RootState) => state.bodyPositionList.selectedBodyPosition
- );
-
- console.log(`[ViewerContainer] rerendered]`);
- // 当 imageUrls 改变时,更新 Redux 中的 allViewers
- useEffect(() => {
- if (imageUrls.length > 0 && JSON.stringify(imageUrls) !== JSON.stringify(allViewerUrls)) {
- dispatch(setAllViewers(imageUrls));
- }
- }, [imageUrls, allViewerUrls, dispatch]);
- // 将 imageUrl 转换为 viewportId
- const getViewportIdByUrl = (url: string): string | null => {
- return `viewport-${url}`;
- };
- const renderViewers = (start: number, end: number) => {
- console.log(`Rendering viewers from ${start} to ${end}`);
- return imageUrls.slice(start, end).map((url, index) => (
- <div
- key={start + index}
- onClick={(event) => handleSelectViewer(url, event)}
- >
- <StackViewer
- key={start + index}
- imageIndex={0}
- imageUrls={[url]}
- viewportId={getViewportIdByUrl(url) as string}
- renderingEngineId={renderingEngineId}
- selected={selectedViewerUrls.includes(url)}
- />
- </div>
- ));
- };
- useEffect(() => {
- renderGrid();
- }, [selectedBodyPosition, gridLayout]);
- useEffect(() => {
- if (action) {
- console.log(
- `[ViewerContainer] 处理功能操作: ${action}, selectedViewers:`,
- selectedViewerUrls
- );
-
- // 将选中的 imageUrl 转换为 viewportId
- const selectedViewportIds = selectedViewerUrls
- .map(getViewportIdByUrl)
- .filter((id): id is string => id !== null);
- // Handle the action
- switch (action) {
- case 'Add L Mark':
- selectedViewportIds.forEach((viewportId) => {
- addLMark(viewportId);
- });
- break;
- case 'Add R Mark':
- selectedViewportIds.forEach((viewportId) => {
- addRLabel(viewportId);
- });
- console.log('Adding R Mark');
- break;
- case 'Delete Selected Mark': {
- selectedViewportIds.forEach((viewportId) => {
- deleteSelectedMark(viewportId);
- });
- break;
- }
- case 'Horizontal Flip': {
- console.log(`开始竖直翻转 in viewer container : ${JSON.stringify(selectedViewportIds)}`);
- selectedViewportIds.forEach((viewportId) => {
- HorizontalFlip(viewportId);
- });
- break;
- }
- case 'Vertical Flip': {
- console.log(`开始水平翻转 in viewer container : ${JSON.stringify(selectedViewportIds)}`);
- selectedViewportIds.forEach((viewportId) => {
- VerticalFlip(viewportId);
- });
- break;
- }
- case 'Rotate Counterclockwise 90': {
- selectedViewportIds.forEach((viewportId) => {
- RotateCounterclockwise90(viewportId);
- });
- break;
- }
- case 'Rotate Clockwise 90':
- selectedViewportIds.forEach((viewportId) => {
- RotateClockwise90(viewportId);
- });
- break;
- case 'Rotate Any Angle':
- selectedViewportIds.forEach((viewportId) => {
- rotateAnyAngle(viewportId);
- });
- break;
- case 'AddMask':
- selectedViewportIds.forEach((viewportId) => {
- addMask(viewportId);
- });
- break;
- case 'Delete Digital Mask':
- selectedViewportIds.forEach((viewportId) => {
- remoteMask(viewportId);
- });
- break;
- case 'Adjust Brightness and Contrast':
- selectedViewportIds.forEach((viewportId) => {
- adjustBrightnessAndContrast(viewportId);
- });
- break;
- case 'Crop Selected Area':
- console.log('Cropping Selected Area');
- break;
- case 'Delete Mask':
- console.log('Deleting Mask');
- break;
- case 'Image Comparison':
- console.log('Comparing Images');
- break;
- case 'Invert Contrast':
- selectedViewportIds.forEach((viewportId) => {
- invertContrast(viewportId);
- });
- console.log('Inverting Contrast');
- break;
- case '1x1 Layout':
- dispatch(setGridLayoutAction('1x1'));
- console.log(`1x1 Layout`);
- break;
- case '1x2 Layout':
- dispatch(setGridLayoutAction('1x2'));
- break;
- case '2x1 Layout':
- dispatch(setGridLayoutAction('2x1'));
- break;
- case '2x2 Layout':
- dispatch(setGridLayoutAction('2x2'));
- break;
- case 'Magnifier': {
- selectedViewportIds.forEach((viewportId) => {
- activateMagnifier(viewportId);
- });
- break;
- }
- case 'Fit Size': {
- selectedViewportIds.forEach((viewportId) => {
- fitImageSize(viewportId);
- });
- console.log('Fitting Image Size');
- break;
- }
- case 'Original Size': {
- selectedViewportIds.forEach((viewportId) => {
- setOriginalSize(viewportId);
- });
- console.log('Setting Image to Original Size');
- break;
- }
- case 'Zoom Image':
- console.log('Zooming Image');
- break;
- case 'Reset Cursor':
- console.log('Resetting Cursor');
- break;
- case 'Pan':
- console.log('Panning Image');
- break;
- case 'Invert Image':
- selectedViewportIds.forEach((viewportId) => {
- InvertImage(viewportId);
- });
- break;
- case 'Reset Image':
- selectedViewportIds.forEach((viewportId) => {
- ResetImage(viewportId);
- });
- break;
- case 'Snapshot':
- console.log('Taking Snapshot');
- break;
- case 'Advanced Processing':
- console.log('Performing Advanced Processing');
- break;
- case 'Musician':
- console.log('Activating Musician');
- break;
- case 'Image Measurement':
- console.log('Measuring Image');
- break;
- case 'More':
- console.log('Showing More Options');
- break;
- case 'Apply Colormap':
- selectedViewportIds.forEach((viewportId) => {
- ApplyColormap(viewportId);
- });
- break;
- // ==================== 线段测量相关操作 ====================
- case '线段测量':
- if (selectedViewportIds.length > 0) {
- selectedViewportIds.forEach((viewportId) => {
- activateLengthMeasurement(viewportId);
- });
- } else {
- // 如果没有选中的 viewport,为所有可见的 viewport 激活
- const visibleViewportCount = getVisibleViewportCount();
- for (let i = 0; i < visibleViewportCount; i++) {
- activateLengthMeasurement(`viewport-${i}`);
- }
- }
- console.log('Activating Length Measurement');
- break;
- case '清除测量':
- if (selectedViewportIds.length > 0) {
- selectedViewportIds.forEach((viewportId) => {
- clearLengthMeasurements(viewportId);
- });
- } else {
- // 如果没有选中的 viewport,清除所有可见 viewport 的测量
- const visibleViewportCount = getVisibleViewportCount();
- for (let i = 0; i < visibleViewportCount; i++) {
- clearLengthMeasurements(`viewport-${i}`);
- }
- }
- console.log('Clearing Length Measurements');
- break;
- case '停用线段测量':
- if (selectedViewportIds.length > 0) {
- selectedViewportIds.forEach((viewportId) => {
- deactivateLengthMeasurement(viewportId);
- });
- } else {
- // 如果没有选中的 viewport,停用所有可见 viewport 的测量工具
- const visibleViewportCount = getVisibleViewportCount();
- for (let i = 0; i < visibleViewportCount; i++) {
- deactivateLengthMeasurement(`viewport-${i}`);
- }
- }
- console.log('Deactivating Length Measurement');
- break;
- default:
- break;
- }
- dispatch(clearAction()); //清理后可连续同一个action触发响应
- }
- }, [action, selectedViewerUrls, dispatch]);
- // ==================== 测量面板 Action 处理 ====================
- useEffect(() => {
- if (measurementAction) {
- console.log(`[ViewerContainer] 处理测量操作: ${measurementAction}`);
- // 将选中的 imageUrl 转换为 viewportId
- const selectedViewportIds = selectedViewerUrls
- .map(getViewportIdByUrl)
- .filter((id): id is string => id !== null);
- // 处理测量相关操作
- switch (measurementAction) {
- case '线段测量':
- if (selectedViewportIds.length > 0) {
- selectedViewportIds.forEach((viewportId) => {
- const success = activateLengthMeasurement(viewportId);
- if (success) {
- dispatch(
- setToolActive({
- toolName: 'LengthTool',
- viewportId: viewportId,
- })
- );
- }
- });
- } else {
- // 如果没有选中的 viewport,为所有可见的 viewport 激活
- const visibleViewportCount = getVisibleViewportCount();
- for (let i = 0; i < visibleViewportCount; i++) {
- const success = activateLengthMeasurement(`viewport-${i}`);
- if (success) {
- dispatch(
- setToolActive({
- toolName: 'LengthTool',
- viewportId: `viewport-${i}`,
- })
- );
- }
- }
- }
- console.log('Activating Length Measurement from MeasurementPanel');
- break;
- case '清除测量':
- if (selectedViewportIds.length > 0) {
- MeasurementToolManager.clearLengthMeasurementsForViewports(selectedViewportIds);
- MeasurementToolManager.clearAngleMeasurementsForViewports(selectedViewportIds);
- }
- console.log('Clearing Length Measurements from MeasurementPanel');
- break;
- case '停用线段测量':
- if (selectedViewportIds.length > 0) {
- selectedViewportIds.forEach((viewportId) => {
- const success = deactivateLengthMeasurement(viewportId);
- if (success) {
- dispatch(
- setToolInactive({
- toolName: 'LengthTool',
- viewportId: viewportId,
- })
- );
- }
- });
- }
- console.log('Deactivating Length Measurement from MeasurementPanel');
- break;
- case '角度测量':
- console.log(`开始角度测量`);
- if (selectedViewportIds.length > 0) {
- selectedViewportIds.forEach((viewportId) => {
- const success = activateAngleMeasurement(viewportId);
- if (success) {
- console.log(`激活角度测量工具成功`);
- dispatch(
- setToolActive({
- toolName: 'AngleTool',
- viewportId: viewportId,
- })
- );
- }
- });
- }
- console.log('Activating Angle Measurement from MeasurementPanel');
- break;
- case '测量校正':
- console.log('Measurement Calibration - 功能待实现');
- break;
- default:
- console.log(`未处理的测量操作: ${measurementAction}`);
- break;
- }
- // 清理测量 action
- dispatch(clearMeasurementAction());
- }
- }, [measurementAction, selectedViewerUrls, gridLayout, dispatch]);
- /**
- * 获取当前可见的 viewport 数量
- */
- const getVisibleViewportCount = (): number => {
- switch (gridLayout) {
- case '1x1':
- return 1;
- case '1x2':
- case '2x1':
- return Math.min(2, imageUrls.length);
- case '2x2':
- return Math.min(4, imageUrls.length);
- default:
- return 1;
- }
- };
- const handleSelectViewer = (imageUrl: string, event: React.MouseEvent) => {
- console.log(`handleSelectViewer : ${imageUrl}`);
- console.log(`selectedViewers 旧值:`, selectedViewerUrls);
- dispatch(toggleViewerSelection(imageUrl));
- };
- const renderGrid = () => {
- console.log('Rendering layout', gridLayout);
- switch (gridLayout) {
- case '1x1': {
- //变成单分格时,自动选中第一个
- dispatch(toggleViewerSelection(allViewerUrls[0]));
- return (
- <div
- className="h-full w-full"
- style={{ display: 'grid', gridTemplateColumns: '1fr' }}
- >
- {renderViewers(0, 1)}
- </div>
- );
- break;
- }
- case '1x2':
- return (
- <div
- className="h-full w-full"
- style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}
- >
- {renderViewers(0, 2)}
- </div>
- );
- case '2x1':
- return (
- <div className="h-full w-full grid grid-cols-1 grid-rows-2">
- {renderViewers(0, 2)}
- </div>
- );
- case '2x2':
- return (
- <div
- className="h-full w-full"
- style={{
- display: 'grid',
- gridTemplateColumns: '1fr 1fr',
- gridTemplateRows: '1fr 1fr',
- }}
- >
- {renderViewers(0, 4)}
- </div>
- );
- default:
- return null;
- }
- };
- return <>{renderGrid()}</>;
- };
- export default ViewerContainer;
|