|
@@ -1,8 +1,49 @@
|
|
|
-import React, { useState, useEffect } from 'react';
|
|
|
+import React, { useState, useEffect, ReactElement } from 'react';
|
|
|
import StackViewer from './viewers/stack.image.viewer';
|
|
|
import { useSelector, useDispatch } from 'react-redux';
|
|
|
-import { RootState } from '@/states/store';
|
|
|
+import store, { RootState } from '@/states/store';
|
|
|
import { clearAction } from '@/states/view/functionAreaSlice';
|
|
|
+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';
|
|
|
+
|
|
|
+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[];
|
|
@@ -10,31 +51,49 @@ interface ViewerContainerProps {
|
|
|
|
|
|
const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
|
|
|
const [selectedViewers, setSelectedViewers] = useState<number[]>([]);
|
|
|
- const [gridLayout, setGridLayout] = useState<'1x1' | '1x2' | '2x1' | '2x2'>(
|
|
|
- '1x1'
|
|
|
- );
|
|
|
+ const [gridLayout, setGridLayout] = useState<string>('1x1');
|
|
|
const action = useSelector((state: RootState) => state.functionArea.action);
|
|
|
const dispatch = useDispatch();
|
|
|
+ // eslint-disable-next-line
|
|
|
+ const [viewersState, setViewersState] = useState<ReactElement[]>([]);
|
|
|
+ console.log(`[ViewerContainer] rerendered]`);
|
|
|
|
|
|
- const viewers = imageUrls.map((url, index) => (
|
|
|
- <div
|
|
|
- key={index}
|
|
|
- onClick={() => handleSelectViewer(index)}
|
|
|
- style={{
|
|
|
- border: selectedViewers.includes(index)
|
|
|
- ? '2px solid blue'
|
|
|
- : '1px solid gray',
|
|
|
- cursor: 'pointer',
|
|
|
- }}
|
|
|
- >
|
|
|
- <StackViewer
|
|
|
+ const renderViewers = (start: number, end: number) => {
|
|
|
+ return imageUrls.slice(start, end).map((url, index) => (
|
|
|
+ <div
|
|
|
key={index}
|
|
|
- imageIndex={0}
|
|
|
- imageUrls={[url]}
|
|
|
- viewportId={`viewport-${index}`}
|
|
|
- />
|
|
|
- </div>
|
|
|
- ));
|
|
|
+ onClick={(event) => handleSelectViewer(index, event)}
|
|
|
+ style={{
|
|
|
+ border: selectedViewers.includes(index)
|
|
|
+ ? '2px solid blue'
|
|
|
+ : '1px solid gray',
|
|
|
+ cursor: 'pointer',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ checked={selectedViewers.includes(index)}
|
|
|
+ onChange={(event) => handleCheckboxChange(index, event)}
|
|
|
+ style={{ marginRight: '8px' }}
|
|
|
+ />
|
|
|
+ <StackViewer
|
|
|
+ key={index}
|
|
|
+ imageIndex={0}
|
|
|
+ imageUrls={[url]}
|
|
|
+ viewportId={`viewport-${index}`}
|
|
|
+ renderingEngineId={renderingEngineId}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ ));
|
|
|
+ };
|
|
|
+
|
|
|
+ // useEffect(() => {
|
|
|
+
|
|
|
+ // }, [imageUrls]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ renderGrid();
|
|
|
+ }, [viewersState]);
|
|
|
|
|
|
useEffect(() => {
|
|
|
if (action) {
|
|
@@ -42,96 +101,97 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
|
|
|
switch (action) {
|
|
|
case 'Add L Mark':
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.addLMark();
|
|
|
+ viewersState[index].props.addLMark();
|
|
|
});
|
|
|
break;
|
|
|
case 'Add R Mark':
|
|
|
// Implement the logic to add an R mark
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.addRLabel();
|
|
|
+ viewersState[index].props.addRLabel();
|
|
|
});
|
|
|
console.log('Adding R Mark');
|
|
|
break;
|
|
|
case 'Delete Selected Mark': {
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.deleteSelectedMark();
|
|
|
+ viewersState[index].props.deleteSelectedMark();
|
|
|
});
|
|
|
break;
|
|
|
}
|
|
|
case 'Horizontal Flip': {
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.HorizontalFlip();
|
|
|
+ viewersState[index].props.HorizontalFlip();
|
|
|
});
|
|
|
break;
|
|
|
}
|
|
|
case 'Vertical Flip': {
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.VerticalFlip();
|
|
|
+ viewersState[index].props.VerticalFlip();
|
|
|
});
|
|
|
break;
|
|
|
}
|
|
|
case 'Rotate Counterclockwise 90': {
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.RotateCounterclockwise90();
|
|
|
+ viewersState[index].props.RotateCounterclockwise90();
|
|
|
});
|
|
|
break;
|
|
|
}
|
|
|
case 'Rotate Clockwise 90':
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.RotateClockwise90();
|
|
|
+ viewersState[index].props.RotateClockwise90();
|
|
|
});
|
|
|
break;
|
|
|
case 'Rotate Any Angle':
|
|
|
// Implement the logic to rotate the image by any angle
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.rotateAnyAngle();
|
|
|
+ viewersState[index].props.rotateAnyAngle();
|
|
|
});
|
|
|
break;
|
|
|
case 'AddMask':
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.addMask();
|
|
|
+ viewersState[index].props.addMask();
|
|
|
});
|
|
|
break;
|
|
|
case 'Delete Digital Mask':
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.remoteMask();
|
|
|
+ viewersState[index].props.remoteMask();
|
|
|
});
|
|
|
break;
|
|
|
case 'Adjust Brightness and Contrast':
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.adjustBrightnessAndContrast();
|
|
|
+ viewersState[index].props.adjustBrightnessAndContrast();
|
|
|
});
|
|
|
break;
|
|
|
case 'Crop Selected Area':
|
|
|
// Implement the logic to crop the selected area
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.cropSelectedArea();
|
|
|
+ viewersState[index].props.cropSelectedArea();
|
|
|
});
|
|
|
console.log('Cropping Selected Area');
|
|
|
break;
|
|
|
case 'Delete Mask':
|
|
|
// Implement the logic to delete the mask
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.deleteMask();
|
|
|
+ viewersState[index].props.deleteMask();
|
|
|
});
|
|
|
console.log('Deleting Mask');
|
|
|
break;
|
|
|
case 'Image Comparison':
|
|
|
// Implement the logic for image comparison
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.imageComparison();
|
|
|
+ viewersState[index].props.imageComparison();
|
|
|
});
|
|
|
console.log('Comparing Images');
|
|
|
break;
|
|
|
case 'Invert Contrast':
|
|
|
// Implement the logic to invert the contrast
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.invertContrast();
|
|
|
+ viewersState[index].props.invertContrast();
|
|
|
});
|
|
|
console.log('Inverting Contrast');
|
|
|
break;
|
|
|
case '1x1 Layout':
|
|
|
setGridLayout('1x1');
|
|
|
+ // renderGrid('1x1');
|
|
|
console.log(`1x1 Layout`);
|
|
|
break;
|
|
|
case '1x2 Layout':
|
|
@@ -146,14 +206,14 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
|
|
|
case 'Magnifier': {
|
|
|
// Implement the logic for magnifier
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.activateMagnifier();
|
|
|
+ viewersState[index].props.activateMagnifier();
|
|
|
});
|
|
|
break;
|
|
|
}
|
|
|
case 'Fit Size': {
|
|
|
// Implement the logic to fit the image size
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.fitImageSize();
|
|
|
+ viewersState[index].props.fitImageSize();
|
|
|
});
|
|
|
console.log('Fitting Image Size');
|
|
|
break;
|
|
@@ -162,79 +222,79 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
|
|
|
// Implement the logic to set the image to original size
|
|
|
console.log('Setting Image to Original Size');
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.setOriginalSize();
|
|
|
+ viewersState[index].props.setOriginalSize();
|
|
|
});
|
|
|
break;
|
|
|
}
|
|
|
case 'Zoom Image':
|
|
|
// Implement the logic to zoom the image
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.zoomImage();
|
|
|
+ viewersState[index].props.zoomImage();
|
|
|
});
|
|
|
console.log('Zooming Image');
|
|
|
break;
|
|
|
case 'Reset Cursor':
|
|
|
// Implement the logic to reset the cursor
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.resetCursor();
|
|
|
+ viewersState[index].props.resetCursor();
|
|
|
});
|
|
|
console.log('Resetting Cursor');
|
|
|
break;
|
|
|
case 'Pan':
|
|
|
// Implement the logic to pan the image
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.panImage();
|
|
|
+ viewersState[index].props.panImage();
|
|
|
});
|
|
|
console.log('Panning Image');
|
|
|
break;
|
|
|
case 'Invert Image':
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.InvertImage();
|
|
|
+ viewersState[index].props.InvertImage();
|
|
|
});
|
|
|
break;
|
|
|
case 'Reset Image':
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.ResetImage();
|
|
|
+ viewersState[index].props.ResetImage();
|
|
|
});
|
|
|
break;
|
|
|
case 'Snapshot':
|
|
|
// Implement the logic to take a snapshot
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.takeSnapshot();
|
|
|
+ viewersState[index].props.takeSnapshot();
|
|
|
});
|
|
|
console.log('Taking Snapshot');
|
|
|
break;
|
|
|
case 'Advanced Processing':
|
|
|
// Implement the logic for advanced processing
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.advancedProcessing();
|
|
|
+ viewersState[index].props.advancedProcessing();
|
|
|
});
|
|
|
console.log('Performing Advanced Processing');
|
|
|
break;
|
|
|
case 'Musician':
|
|
|
// Implement the logic for musician
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.activateMusician();
|
|
|
+ viewersState[index].props.activateMusician();
|
|
|
});
|
|
|
console.log('Activating Musician');
|
|
|
break;
|
|
|
case 'Image Measurement':
|
|
|
// Implement the logic for image measurement
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.imageMeasurement();
|
|
|
+ viewersState[index].props.imageMeasurement();
|
|
|
});
|
|
|
console.log('Measuring Image');
|
|
|
break;
|
|
|
case 'More':
|
|
|
// Implement the logic for more options
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.moreOptions();
|
|
|
+ viewersState[index].props.moreOptions();
|
|
|
});
|
|
|
console.log('Showing More Options');
|
|
|
break;
|
|
|
case 'Apply Colormap':
|
|
|
selectedViewers.forEach((index) => {
|
|
|
- viewers[index].props.ApplyColormap();
|
|
|
+ viewersState[index].props.ApplyColormap();
|
|
|
});
|
|
|
break;
|
|
|
default:
|
|
@@ -244,13 +304,33 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
|
|
|
}
|
|
|
}, [action]);
|
|
|
|
|
|
- const handleSelectViewer = (index: number) => {
|
|
|
+ const handleSelectViewer = (index: number, event) => {
|
|
|
setSelectedViewers((prev) =>
|
|
|
prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index]
|
|
|
);
|
|
|
+ console.log(`handleSelectViewer : ${index}`);
|
|
|
+
|
|
|
+ // Directly update the div style using a ref
|
|
|
+ const viewerElement = event.currentTarget;
|
|
|
+ console.log(`viewerElement:`, viewerElement);
|
|
|
+ console.log(`selectedViewers:`, selectedViewers);
|
|
|
+ if (viewerElement) {
|
|
|
+ viewerElement.style.border = selectedViewers.includes(index)
|
|
|
+ ? '1px solid gray'
|
|
|
+ : '2px solid blue';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleCheckboxChange = (index: number, event) => {
|
|
|
+ setSelectedViewers((prev) =>
|
|
|
+ event.target.checked ? [...prev, index] : prev.filter((i) => i !== index)
|
|
|
+ );
|
|
|
+
|
|
|
+ event.target.checked = !event.target.checked;
|
|
|
};
|
|
|
|
|
|
const renderGrid = () => {
|
|
|
+ console.log('Rendering layout', gridLayout);
|
|
|
switch (gridLayout) {
|
|
|
case '1x1':
|
|
|
return (
|
|
@@ -258,7 +338,7 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
|
|
|
className="h-full w-full"
|
|
|
style={{ display: 'grid', gridTemplateColumns: '1fr' }}
|
|
|
>
|
|
|
- {viewers[0]}
|
|
|
+ {renderViewers(0, 1)}
|
|
|
</div>
|
|
|
);
|
|
|
case '1x2':
|
|
@@ -267,13 +347,13 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
|
|
|
className="h-full w-full"
|
|
|
style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}
|
|
|
>
|
|
|
- {viewers.slice(0, 2)}
|
|
|
+ {renderViewers(0, 2)}
|
|
|
</div>
|
|
|
);
|
|
|
case '2x1':
|
|
|
return (
|
|
|
<div className="h-full w-full grid grid-cols-1 grid-rows-2">
|
|
|
- {viewers.slice(0, 2)}
|
|
|
+ {renderViewers(0, 2)}
|
|
|
</div>
|
|
|
);
|
|
|
case '2x2':
|
|
@@ -286,7 +366,7 @@ const ViewerContainer: React.FC<ViewerContainerProps> = ({ imageUrls }) => {
|
|
|
gridTemplateRows: '1fr 1fr',
|
|
|
}}
|
|
|
>
|
|
|
- {viewers.slice(0, 4)}
|
|
|
+ {renderViewers(0, 4)}
|
|
|
</div>
|
|
|
);
|
|
|
default:
|