123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- import React, { useState, useEffect, ReactElement } 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,
- } from './viewers/stack.image.viewer';
- import { useSelector, useDispatch } from 'react-redux';
- import store, { RootState } from '@/states/store';
- import { clearAction } from '@/states/view/functionAreaSlice';
- 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';
- 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);
- const [selectedViewers, setSelectedViewers] = useState<number[]>([]);
- const [gridLayout, setGridLayout] = useState<string>('1x1');
- const action = useSelector((state: RootState) => state.functionArea.action);
- const measurementAction = useSelector(selectCurrentMeasurementAction);
- const dispatch = useDispatch();
- const selectedBodyPosition = useSelector(
- (state: RootState) => state.bodyPositionList.selectedBodyPosition
- );
- const bodyPositions = useSelector(
- (state: RootState) => state.bodyPositionList.bodyPositions
- );
- // eslint-disable-next-line
- const [viewersState, setViewersState] = useState<ReactElement[]>([]);
- console.log(`[ViewerContainer] rerendered]`);
- 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(start + index, event)}
- style={{
- border: selectedViewers.includes(start + 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={start + index}
- imageIndex={0}
- imageUrls={[url]}
- viewportId={`viewport-${start + index}`}
- renderingEngineId={renderingEngineId}
- />
- </div>
- ));
- };
- // useEffect(() => {
- // }, [imageUrls]);
- useEffect(() => {
- renderGrid();
- }, [viewersState, selectedBodyPosition, gridLayout]);
- useEffect(() => {
- if (action) {
- console.log(
- `[ViewerContainer] 处理功能操作: ${action}, selectedViewers:`,
- selectedViewers
- );
- // Handle the action
- switch (action) {
- case 'Add L Mark':
- selectedViewers.forEach((index) => {
- // viewersState[index].props.addLMark();
- addLMark(`viewport-${index}`);
- });
- break;
- case 'Add R Mark':
- selectedViewers.forEach((index) => {
- addRLabel(`viewport-${index}`);
- });
- console.log('Adding R Mark');
- break;
- case 'Delete Selected Mark': {
- selectedViewers.forEach((index) => {
- deleteSelectedMark(`viewport-${index}`);
- });
- break;
- }
- case 'Horizontal Flip': {
- selectedViewers.forEach((index) => {
- HorizontalFlip(`viewport-${index}`);
- });
- break;
- }
- case 'Vertical Flip': {
- selectedViewers.forEach((index) => {
- VerticalFlip(`viewport-${index}`);
- });
- break;
- }
- case 'Rotate Counterclockwise 90': {
- selectedViewers.forEach((index) => {
- RotateCounterclockwise90(`viewport-${index}`);
- });
- break;
- }
- case 'Rotate Clockwise 90':
- selectedViewers.forEach((index) => {
- RotateClockwise90(`viewport-${index}`);
- });
- break;
- case 'Rotate Any Angle':
- selectedViewers.forEach((index) => {
- rotateAnyAngle(`viewport-${index}`);
- });
- break;
- case 'AddMask':
- selectedViewers.forEach((index) => {
- addMask(`viewport-${index}`);
- });
- break;
- case 'Delete Digital Mask':
- selectedViewers.forEach((index) => {
- remoteMask(`viewport-${index}`);
- });
- break;
- case 'Adjust Brightness and Contrast':
- selectedViewers.forEach((index) => {
- adjustBrightnessAndContrast(`viewport-${index}`);
- });
- break;
- case 'Crop Selected Area':
- // selectedViewers.forEach((index) => {
- // cropSelectedArea(`viewport-${index}`);
- // });
- console.log('Cropping Selected Area');
- break;
- case 'Delete Mask':
- // selectedViewers.forEach((index) => {
- // deleteMask(`viewport-${index}`);
- // });
- console.log('Deleting Mask');
- break;
- case 'Image Comparison':
- // selectedViewers.forEach((index) => {
- // imageComparison(`viewport-${index}`);
- // });
- console.log('Comparing Images');
- break;
- case 'Invert Contrast':
- selectedViewers.forEach((index) => {
- invertContrast(`viewport-${index}`);
- });
- console.log('Inverting Contrast');
- break;
- case '1x1 Layout':
- setGridLayout('1x1');
- // renderGrid('1x1');
- console.log(`1x1 Layout`);
- break;
- case '1x2 Layout':
- setGridLayout('1x2');
- break;
- case '2x1 Layout':
- setGridLayout('2x1');
- break;
- case '2x2 Layout':
- setGridLayout('2x2');
- break;
- case 'Magnifier': {
- selectedViewers.forEach((index) => {
- activateMagnifier(`viewport-${index}`);
- });
- break;
- }
- case 'Fit Size': {
- selectedViewers.forEach((index) => {
- fitImageSize(`viewport-${index}`);
- });
- console.log('Fitting Image Size');
- break;
- }
- case 'Original Size': {
- selectedViewers.forEach((index) => {
- setOriginalSize(`viewport-${index}`);
- });
- console.log('Setting Image to Original Size');
- break;
- }
- case 'Zoom Image':
- // selectedViewers.forEach((index) => {
- // zoomImage(`viewport-${index}`);
- // });
- console.log('Zooming Image');
- break;
- case 'Reset Cursor':
- // selectedViewers.forEach((index) => {
- // resetCursor(`viewport-${index}`);
- // });
- console.log('Resetting Cursor');
- break;
- case 'Pan':
- // selectedViewers.forEach((index) => {
- // panImage(`viewport-${index}`);
- // });
- console.log('Panning Image');
- break;
- case 'Invert Image':
- selectedViewers.forEach((index) => {
- InvertImage(`viewport-${index}`);
- });
- break;
- case 'Reset Image':
- selectedViewers.forEach((index) => {
- ResetImage(`viewport-${index}`);
- });
- break;
- case 'Snapshot':
- // selectedViewers.forEach((index) => {
- // takeSnapshot(`viewport-${index}`);
- // });
- console.log('Taking Snapshot');
- break;
- case 'Advanced Processing':
- // selectedViewers.forEach((index) => {
- // advancedProcessing(`viewport-${index}`);
- // });
- console.log('Performing Advanced Processing');
- break;
- case 'Musician':
- // selectedViewers.forEach((index) => {
- // activateMusician(`viewport-${index}`);
- // });
- console.log('Activating Musician');
- break;
- case 'Image Measurement':
- // selectedViewers.forEach((index) => {
- // imageMeasurement(`viewport-${index}`);
- // });
- console.log('Measuring Image');
- break;
- case 'More':
- // selectedViewers.forEach((index) => {
- // moreOptions(`viewport-${index}`);
- // });
- console.log('Showing More Options');
- break;
- case 'Apply Colormap':
- selectedViewers.forEach((index) => {
- ApplyColormap(`viewport-${index}`);
- });
- break;
- // ==================== 线段测量相关操作 ====================
- case '线段测量':
- if (selectedViewers.length > 0) {
- selectedViewers.forEach((index) => {
- activateLengthMeasurement(`viewport-${index}`);
- });
- } else {
- // 如果没有选中的 viewport,为所有可见的 viewport 激活
- const visibleViewportCount = getVisibleViewportCount();
- for (let i = 0; i < visibleViewportCount; i++) {
- activateLengthMeasurement(`viewport-${i}`);
- }
- }
- console.log('Activating Length Measurement');
- break;
- case '清除测量':
- if (selectedViewers.length > 0) {
- selectedViewers.forEach((index) => {
- clearLengthMeasurements(`viewport-${index}`);
- });
- } else {
- // 如果没有选中的 viewport,清除所有可见 viewport 的测量
- const visibleViewportCount = getVisibleViewportCount();
- for (let i = 0; i < visibleViewportCount; i++) {
- clearLengthMeasurements(`viewport-${i}`);
- }
- }
- console.log('Clearing Length Measurements');
- break;
- case '停用线段测量':
- if (selectedViewers.length > 0) {
- selectedViewers.forEach((index) => {
- deactivateLengthMeasurement(`viewport-${index}`);
- });
- } 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, selectedViewers, dispatch]);
- // ==================== 测量面板 Action 处理 ====================
- useEffect(() => {
- if (measurementAction) {
- console.log(`[ViewerContainer] 处理测量操作: ${measurementAction}`);
- // 处理测量相关操作
- switch (measurementAction) {
- case '线段测量':
- if (selectedViewers.length > 0) {
- selectedViewers.forEach((index) => {
- const success = activateLengthMeasurement(`viewport-${index}`);
- if (success) {
- dispatch(
- setToolActive({
- toolName: 'LengthTool',
- viewportId: `viewport-${index}`,
- })
- );
- }
- });
- } 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 (selectedViewers.length > 0) {
- selectedViewers.forEach((index) => {
- clearLengthMeasurements(`viewport-${index}`);
- });
- } else {
- // 如果没有选中的 viewport,清除所有可见 viewport 的测量
- const visibleViewportCount = getVisibleViewportCount();
- for (let i = 0; i < visibleViewportCount; i++) {
- clearLengthMeasurements(`viewport-${i}`);
- }
- }
- console.log('Clearing Length Measurements from MeasurementPanel');
- break;
- case '停用线段测量':
- if (selectedViewers.length > 0) {
- selectedViewers.forEach((index) => {
- const success = deactivateLengthMeasurement(`viewport-${index}`);
- if (success) {
- dispatch(
- setToolInactive({
- toolName: 'LengthTool',
- viewportId: `viewport-${index}`,
- })
- );
- }
- });
- } else {
- // 如果没有选中的 viewport,停用所有可见 viewport 的测量工具
- const visibleViewportCount = getVisibleViewportCount();
- for (let i = 0; i < visibleViewportCount; i++) {
- const success = deactivateLengthMeasurement(`viewport-${i}`);
- if (success) {
- dispatch(
- setToolInactive({
- toolName: 'LengthTool',
- viewportId: `viewport-${i}`,
- })
- );
- }
- }
- }
- console.log('Deactivating Length Measurement from MeasurementPanel');
- break;
- case '角度测量':
- console.log('Angle Measurement - 功能待实现');
- break;
- case '测量校正':
- console.log('Measurement Calibration - 功能待实现');
- break;
- default:
- console.log(`未处理的测量操作: ${measurementAction}`);
- break;
- }
- // 清理测量 action
- dispatch(clearMeasurementAction());
- }
- }, [measurementAction, selectedViewers, 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 = (index: number, event) => {
- setSelectedViewers((prev) => {
- const newSelected = prev.includes(index)
- ? prev.filter((i) => i !== index)
- : [...prev, index];
- console.log(`handleSelectViewer : ${index}`);
- console.log(`selectedViewers 旧值:`, prev);
- console.log(`selectedViewers 新值:`, newSelected);
- // 使用计算后的新值来更新边框,避免闭包陷阱
- const viewerElement = event.currentTarget;
- if (viewerElement) {
- viewerElement.style.border = newSelected.includes(index)
- ? '2px solid blue'
- : '1px solid gray';
- }
- return newSelected;
- });
- };
- // 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': {
- const index = bodyPositions
- .filter((bp) => bp.dview.expose_status === 'Exposed')
- .findIndex(
- (bp) =>
- bp.sop_instance_uid === selectedBodyPosition?.sop_instance_uid
- );
- console.log(`查找到索引:${index}`);
- if (index !== -1) {
- return (
- <div
- className="h-full w-full"
- style={{ display: 'grid', gridTemplateColumns: '1fr' }}
- >
- {renderViewers(index, index + 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;
|