stack.image.viewer.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import React, { useEffect, useRef } from 'react';
  2. import * as cornerstone from '@cornerstonejs/core';
  3. import type { Types } from '@cornerstonejs/core';
  4. import * as cornerstoneTools from '@cornerstonejs/tools';
  5. import * as cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';
  6. import { useSelector } from 'react-redux';
  7. import { RootState } from '@/states/store';
  8. import { SystemMode } from '@/states/systemModeSlice';
  9. import store from '@/states/store';
  10. import { clearAction } from '@/states/view/functionAreaSlice';
  11. import { useDispatch } from 'react-redux';
  12. import { IP_PORT } from '@/API/config';
  13. const { PanTool, WindowLevelTool, StackScrollTool, ZoomTool, LabelTool, ToolGroupManager, Enums: csToolsEnums } = cornerstoneTools;
  14. const { MouseBindings } = csToolsEnums;
  15. let toolGroup: cornerstoneTools.Types.IToolGroup;
  16. let currentViewportId: string;
  17. function registerTools(viewportId, renderingEngineId) {
  18. // Add tools to Cornerstone3D
  19. cornerstoneTools.addTool(PanTool);
  20. cornerstoneTools.addTool(WindowLevelTool);
  21. cornerstoneTools.addTool(StackScrollTool);
  22. cornerstoneTools.addTool(ZoomTool);
  23. cornerstoneTools.addTool(LabelTool);
  24. // Define a tool group
  25. const toolGroupId = 'STACK_TOOL_GROUP_ID';
  26. const toolGroupTmp = ToolGroupManager.createToolGroup(toolGroupId);
  27. if (!toolGroupTmp) {
  28. return;
  29. }
  30. toolGroup = toolGroupTmp;
  31. // Add tools to the tool group
  32. toolGroup.addTool(PanTool.toolName);
  33. toolGroup.addTool(WindowLevelTool.toolName);
  34. toolGroup.addTool(StackScrollTool.toolName);
  35. toolGroup.addTool(ZoomTool.toolName);
  36. toolGroup.addTool(LabelTool.toolName);
  37. // Set the LabelTool as active
  38. // toolGroup.setToolActive(LabelTool.toolName, {
  39. // bindings: [
  40. // {
  41. // mouseButton: MouseBindings.Primary, // Left Click
  42. // },
  43. // ],
  44. // });
  45. // Set the initial state of the tools
  46. // toolGroup.setToolActive(WindowLevelTool.toolName, {
  47. // bindings: [
  48. // {
  49. // mouseButton: MouseBindings.Primary, // Left Click
  50. // },
  51. // ],
  52. // });
  53. toolGroup.setToolActive(PanTool.toolName, {
  54. bindings: [
  55. {
  56. mouseButton: MouseBindings.Auxiliary, // Middle Click
  57. },
  58. ],
  59. });
  60. toolGroup.setToolActive(ZoomTool.toolName, {
  61. bindings: [
  62. {
  63. mouseButton: MouseBindings.Secondary, // Right Click
  64. },
  65. ],
  66. });
  67. toolGroup.setToolActive(StackScrollTool.toolName, {
  68. bindings: [
  69. {
  70. mouseButton: MouseBindings.Wheel, // Mouse Wheel
  71. },
  72. ],
  73. });
  74. toolGroup.addViewport(viewportId, renderingEngineId);
  75. }
  76. function addRLabel(viewportId) {
  77. toolGroup.setToolActive(LabelTool.toolName, {
  78. bindings: [],
  79. });
  80. const element = document.getElementById(viewportId);
  81. const elementHeight = element ? element.getBoundingClientRect().height : 0;
  82. const position: Types.Point3 = [100, elementHeight / 2, 0]; // Example position
  83. const text = 'R'; // Predefined text
  84. LabelTool.hydrate(viewportId, position, text);
  85. toolGroup.setToolPassive(LabelTool.toolName, { removeAllBindings: true });
  86. }
  87. const StackViewer = ({ imageIndex }) => {
  88. const elementRef = useRef<HTMLDivElement>(null);
  89. const action = useSelector((state: RootState) => state.functionArea.action);
  90. const dispatch = useDispatch();
  91. useEffect(() => {
  92. const setup = async () => {
  93. // 初始化 Cornerstone
  94. cornerstone.init();
  95. cornerstoneTools.init();
  96. const state = store.getState();
  97. console.log(`当前系统模式:${state.systemMode.mode}`);
  98. const token =
  99. state.systemMode.mode === SystemMode.Emergency
  100. ? state.product.guest
  101. : state.userInfo.token;
  102. console.log(`token stack.image.viewer: ${token}`);
  103. cornerstoneDICOMImageLoader.init({
  104. maxWebWorkers: navigator.hardwareConcurrency || 1,
  105. errorInterceptor: (error) => {
  106. if (error.status === 401) {
  107. console.error('Authentication failed. Please refresh the token.');
  108. }
  109. console.error(`请求dcm文件出错:${error}`);
  110. },
  111. beforeSend: (xhr, imageId, defaultHeaders) => {
  112. return {
  113. ...defaultHeaders,
  114. Authorization: `Bearer ${token}`,
  115. Language: 'en',
  116. Product: 'DROS',
  117. Source: 'Electron',
  118. };
  119. },
  120. });
  121. // Instantiate a rendering engine
  122. const renderingEngineId = 'myRenderingEngine';
  123. const renderingEngine = new cornerstone.RenderingEngine(renderingEngineId);
  124. const viewportId = 'CT_AXIAL_STACK';
  125. currentViewportId = viewportId;
  126. const viewportInput: cornerstone.Types.PublicViewportInput = {
  127. viewportId,
  128. element: elementRef.current!,
  129. type: cornerstone.Enums.ViewportType.STACK,
  130. };
  131. renderingEngine.enableElement(viewportInput);
  132. registerTools(viewportId, renderingEngineId);
  133. // Get the stack viewport that was created
  134. const viewport = renderingEngine.getViewport(viewportId) as cornerstone.Types.IStackViewport;
  135. // 给定一个dcm文件路径,加载并显示出来
  136. const imageId1 = 'dicomweb:https://ohif-assets-new.s3.us-east-1.amazonaws.com/ACRIN-Regular/CT+CT+IMAGES/CT000000.dcm';
  137. const imageId2 = 'dicomweb:https://ohif-assets-new.s3.us-east-1.amazonaws.com/ACRIN-Regular/CT+CT+IMAGES/CT000005.dcm';
  138. const imageId3 = 'dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm';
  139. const imageId4 = `dicomweb:${IP_PORT}/dr/api/v1/auth/image/dcm/CTImage.dcm`;
  140. const imageId5 = `dicomweb:${IP_PORT}/dr/api/v1/pub/dcm/CTImage.dcm`;
  141. const imageId6 = `dicomweb:${IP_PORT}/dr/api/v1/auth/image/dcm/CTImage.dcm`;
  142. const imageId7 = `dicomweb:${IP_PORT}/dr/api/v1/pub/dcm/CTImage.dcm`;
  143. imageIndex = 6;
  144. await viewport.setStack([imageId1, imageId2, imageId3, imageId4, imageId5, imageId6, imageId7], imageIndex);
  145. viewport.render();
  146. };
  147. setup();
  148. }, [elementRef, imageIndex]);
  149. useEffect(() => {
  150. if (action) {
  151. // Handle the action
  152. switch (action) {
  153. case 'Add L Mark':
  154. // Implement the logic to add an L mark
  155. console.log('Adding L Mark');
  156. toolGroup.setToolActive(LabelTool.toolName, {
  157. bindings: [
  158. // {
  159. // mouseButton: MouseBindings.Primary, // Left Click
  160. // },
  161. ],
  162. });
  163. const position: Types.Point3 = [100, 100, 0]; // Example position
  164. const text = 'L'; // Predefined text
  165. LabelTool.hydrate(currentViewportId, position, text);
  166. toolGroup.setToolPassive(LabelTool.toolName, { removeAllBindings: true });
  167. // const enabledElement = cornerstone.getEnabledElementByViewportId(currentViewportId);
  168. // cursors.elementCursor.resetElementCursor(elementRef.current as HTMLDivElement);
  169. break;
  170. case 'Add R Mark':
  171. // Implement the logic to add an R mark
  172. addRLabel(currentViewportId);
  173. console.log('Adding R Mark');
  174. break;
  175. case 'Delete Selected Mark':
  176. // Implement the logic to delete the selected mark
  177. const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId).viewport;
  178. const allAnnotations = cornerstoneTools.annotation.state.getAllAnnotations();
  179. // const allAnnotations = cornerstoneTools.annotation.state.getAnnotations(LabelTool.toolName, element.element);
  180. console.log(`allAnnotations 数量:${allAnnotations.length}`);
  181. for (const annotation of allAnnotations) {
  182. if (annotation.data.text === 'L' || annotation.data.text === 'R') {
  183. console.log(`Deleting annotation with UID: ${annotation.annotationUID}`);
  184. cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID!);
  185. }
  186. // cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID!);
  187. }
  188. // 触发视口重新渲染
  189. viewport.render();
  190. console.log('Deleting Selected Mark');
  191. break;
  192. case 'Horizontal Flip':
  193. // Implement the logic to flip the image horizontally
  194. console.log('Flipping Image Horizontally');
  195. break;
  196. // case 'Vertical Flip':
  197. // { // Implement the logic to flip the image vertically
  198. // console.log('Flipping Image Vertically');
  199. // }
  200. // break;
  201. case 'Rotate Counterclockwise 90':
  202. // Implement the logic to rotate the image counterclockwise
  203. console.log('Rotating Image Counterclockwise 90°');
  204. break;
  205. case 'Rotate Clockwise 90':
  206. // Implement the logic to rotate the image clockwise
  207. console.log('Rotating Image Clockwise 90°');
  208. break;
  209. case 'Rotate Any Angle':
  210. // Implement the logic to rotate the image by any angle
  211. console.log('Rotating Image by Any Angle');
  212. break;
  213. case 'Crop Image':
  214. // Implement the logic to crop the image
  215. console.log('Cropping Image');
  216. break;
  217. case 'Delete Digital Mask':
  218. // Implement the logic to delete the digital mask
  219. console.log('Deleting Digital Mask');
  220. break;
  221. case 'Adjust Brightness and Contrast':
  222. // Implement the logic to adjust brightness and contrast
  223. console.log('Adjusting Brightness and Contrast');
  224. break;
  225. case 'Crop Selected Area':
  226. // Implement the logic to crop the selected area
  227. console.log('Cropping Selected Area');
  228. break;
  229. case 'Delete Mask':
  230. // Implement the logic to delete the mask
  231. console.log('Deleting Mask');
  232. break;
  233. case 'Image Comparison':
  234. // Implement the logic for image comparison
  235. console.log('Comparing Images');
  236. break;
  237. case 'Invert Contrast':
  238. // Implement the logic to invert the contrast
  239. {
  240. const viewport = cornerstone.getEnabledElementByViewportId(currentViewportId).viewport;
  241. const targetBool = !viewport.getProperties().invert;
  242. viewport.setProperties({
  243. invert: targetBool,
  244. });
  245. viewport.render();
  246. }
  247. console.log('Inverting Contrast');
  248. break;
  249. case '1x1 Layout':
  250. // Implement the logic for 1x1 layout
  251. console.log('Setting 1x1 Layout');
  252. break;
  253. case '1x2 Layout':
  254. // Implement the logic for 1x2 layout
  255. console.log('Setting 1x2 Layout');
  256. break;
  257. case '2x2 Layout':
  258. // Implement the logic for 2x2 layout
  259. console.log('Setting 2x2 Layout');
  260. break;
  261. case '4x4 Layout':
  262. // Implement the logic for 4x4 layout
  263. console.log('Setting 4x4 Layout');
  264. break;
  265. case 'Magnifier':
  266. // Implement the logic for magnifier
  267. console.log('Activating Magnifier');
  268. break;
  269. case 'Fit Size':
  270. // Implement the logic to fit the image size
  271. console.log('Fitting Image Size');
  272. break;
  273. case 'Original Size':
  274. // Implement the logic to set the image to original size
  275. console.log('Setting Image to Original Size');
  276. break;
  277. case 'Zoom Image':
  278. // Implement the logic to zoom the image
  279. console.log('Zooming Image');
  280. break;
  281. case 'Reset Cursor':
  282. // Implement the logic to reset the cursor
  283. console.log('Resetting Cursor');
  284. break;
  285. case 'Pan':
  286. // Implement the logic to pan the image
  287. console.log('Panning Image');
  288. break;
  289. case 'Invert Image':
  290. // Implement the logic to invert the image
  291. console.log('Inverting Image');
  292. break;
  293. case 'Reset Image':
  294. // Implement the logic to reset the image
  295. console.log('Resetting Image');
  296. break;
  297. case 'Snapshot':
  298. // Implement the logic to take a snapshot
  299. console.log('Taking Snapshot');
  300. break;
  301. case 'Advanced Processing':
  302. // Implement the logic for advanced processing
  303. console.log('Performing Advanced Processing');
  304. break;
  305. case 'Musician':
  306. // Implement the logic for musician
  307. console.log('Activating Musician');
  308. break;
  309. case 'Image Measurement':
  310. // Implement the logic for image measurement
  311. console.log('Measuring Image');
  312. break;
  313. case 'More':
  314. // Implement the logic for more options
  315. console.log('Showing More Options');
  316. break;
  317. default:
  318. break;
  319. }
  320. dispatch(clearAction());//清理后可连续同一个action触发响应
  321. }
  322. }, [action]);
  323. return (
  324. <div
  325. ref={elementRef}
  326. style={{ width: '100%', height: '100%', backgroundColor: '#000' }}
  327. />
  328. );
  329. };
  330. export default StackViewer;