measurementToolManager.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. import * as cornerstone from '@cornerstonejs/core';
  2. import * as cornerstoneTools from '@cornerstonejs/tools';
  3. import TibialPlateauAngleTool from '@/components/measures/TibialPlateauAngleTool';
  4. const {
  5. ToolGroupManager,
  6. LengthTool,
  7. AngleTool,
  8. WindowLevelTool,
  9. MagnifyTool,
  10. Enums: csToolsEnums,
  11. } = cornerstoneTools;
  12. const { MouseBindings } = csToolsEnums;
  13. /**
  14. * 测量工具管理器
  15. * 统一管理所有测量相关的工具操作
  16. */
  17. export class MeasurementToolManager {
  18. /**
  19. * 根据 viewportId 获取对应的工具组
  20. */
  21. static getToolGroup(
  22. viewportId: string
  23. ): cornerstoneTools.Types.IToolGroup | undefined {
  24. const toolGroupId = `STACK_TOOL_GROUP_ID_${viewportId}`;
  25. const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
  26. if (!toolGroup) {
  27. console.warn(
  28. `[MeasurementToolManager] Tool group not found for viewport: ${viewportId}`
  29. );
  30. }
  31. return toolGroup;
  32. }
  33. /**
  34. * 激活线段测量工具
  35. */
  36. static activateLengthTool(viewportId: string): boolean {
  37. const toolGroup = this.getToolGroup(viewportId);
  38. if (!toolGroup) return false;
  39. try {
  40. // 停用其他可能冲突的工具
  41. toolGroup.setToolPassive(WindowLevelTool.toolName, {
  42. removeAllBindings: true,
  43. });
  44. toolGroup.setToolPassive(MagnifyTool.toolName, {
  45. removeAllBindings: true,
  46. });
  47. // 激活线段测量工具
  48. toolGroup.setToolActive(LengthTool.toolName, {
  49. bindings: [{ mouseButton: MouseBindings.Primary }],
  50. });
  51. console.log(
  52. `[MeasurementToolManager] Length tool activated for viewport: ${viewportId}`
  53. );
  54. return true;
  55. } catch (error) {
  56. console.error(
  57. `[MeasurementToolManager] Error activating length tool:`,
  58. error
  59. );
  60. return false;
  61. }
  62. }
  63. /**
  64. * 停用线段测量工具
  65. */
  66. static deactivateLengthTool(viewportId: string): boolean {
  67. const toolGroup = this.getToolGroup(viewportId);
  68. if (!toolGroup) return false;
  69. try {
  70. toolGroup.setToolPassive(LengthTool.toolName, {
  71. removeAllBindings: true,
  72. });
  73. console.log(
  74. `[MeasurementToolManager] Length tool deactivated for viewport: ${viewportId}`
  75. );
  76. return true;
  77. } catch (error) {
  78. console.error(
  79. `[MeasurementToolManager] Error deactivating length tool:`,
  80. error
  81. );
  82. return false;
  83. }
  84. }
  85. /**
  86. * 检查线段测量工具是否处于激活状态
  87. */
  88. static isLengthToolActive(viewportId: string): boolean {
  89. const toolGroup = this.getToolGroup(viewportId);
  90. if (!toolGroup) return false;
  91. try {
  92. const activeTool = toolGroup.getActivePrimaryMouseButtonTool();
  93. return activeTool === LengthTool.toolName;
  94. } catch (error) {
  95. console.error(
  96. `[MeasurementToolManager] Error checking tool state:`,
  97. error
  98. );
  99. return false;
  100. }
  101. }
  102. /**
  103. * 切换线段测量工具状态
  104. */
  105. static toggleLengthTool(viewportId: string): boolean {
  106. const isActive = this.isLengthToolActive(viewportId);
  107. if (isActive) {
  108. return this.deactivateLengthTool(viewportId);
  109. } else {
  110. return this.activateLengthTool(viewportId);
  111. }
  112. }
  113. /**
  114. * 清除指定 viewport 的所有线段测量标注
  115. */
  116. static clearLengthMeasurements(viewportId: string): boolean {
  117. try {
  118. const viewport =
  119. cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
  120. if (!viewport) {
  121. console.warn(
  122. `[MeasurementToolManager] Viewport not found: ${viewportId}`
  123. );
  124. return false;
  125. }
  126. const annotations = cornerstoneTools.annotation.state.getAnnotations(
  127. LengthTool.toolName,
  128. viewport.element
  129. );
  130. let removedCount = 0;
  131. annotations.forEach((annotation) => {
  132. if (annotation.annotationUID) {
  133. cornerstoneTools.annotation.state.removeAnnotation(
  134. annotation.annotationUID
  135. );
  136. removedCount++;
  137. }
  138. });
  139. viewport.render();
  140. console.log(
  141. `[MeasurementToolManager] Cleared ${removedCount} length measurements for viewport: ${viewportId}`
  142. );
  143. return true;
  144. } catch (error) {
  145. console.error(
  146. `[MeasurementToolManager] Error clearing measurements:`,
  147. error
  148. );
  149. return false;
  150. }
  151. }
  152. /**
  153. * 获取指定 viewport 的所有线段测量结果
  154. */
  155. // eslint-disable-next-line
  156. static getLengthMeasurements(viewportId: string): any[] {
  157. try {
  158. const viewport =
  159. cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
  160. if (!viewport) {
  161. console.warn(
  162. `[MeasurementToolManager] Viewport not found: ${viewportId}`
  163. );
  164. return [];
  165. }
  166. const annotations = cornerstoneTools.annotation.state.getAnnotations(
  167. LengthTool.toolName,
  168. viewport.element
  169. );
  170. return annotations.map((annotation) => ({
  171. annotationUID: annotation.annotationUID,
  172. length: annotation.data?.cachedStats?.length || 0,
  173. unit: annotation.data?.cachedStats?.unit || 'mm',
  174. points: annotation.data?.handles?.points || [],
  175. }));
  176. } catch (error) {
  177. console.error(
  178. `[MeasurementToolManager] Error getting measurements:`,
  179. error
  180. );
  181. return [];
  182. }
  183. }
  184. /**
  185. * 为多个 viewport 批量激活线段测量工具
  186. */
  187. static activateLengthToolForViewports(viewportIds: string[]): boolean[] {
  188. return viewportIds.map((viewportId) => this.activateLengthTool(viewportId));
  189. }
  190. /**
  191. * 为多个 viewport 批量停用线段测量工具
  192. */
  193. static deactivateLengthToolForViewports(viewportIds: string[]): boolean[] {
  194. return viewportIds.map((viewportId) =>
  195. this.deactivateLengthTool(viewportId)
  196. );
  197. }
  198. /**
  199. * 为多个 viewport 批量清除线段测量
  200. */
  201. static clearLengthMeasurementsForViewports(viewportIds: string[]): boolean[] {
  202. return viewportIds.map((viewportId) =>
  203. this.clearLengthMeasurements(viewportId)
  204. );
  205. }
  206. /**
  207. * 激活角度测量工具
  208. */
  209. static activateAngleTool(viewportId: string): boolean {
  210. const toolGroup = this.getToolGroup(viewportId);
  211. if (!toolGroup) return false;
  212. try {
  213. // 停用其他可能冲突的工具
  214. toolGroup.setToolPassive(WindowLevelTool.toolName, {
  215. removeAllBindings: true,
  216. });
  217. toolGroup.setToolPassive(MagnifyTool.toolName, {
  218. removeAllBindings: true,
  219. });
  220. toolGroup.setToolPassive(LengthTool.toolName, {
  221. removeAllBindings: true,
  222. });
  223. // 激活角度测量工具
  224. toolGroup.setToolActive(AngleTool.toolName, {
  225. bindings: [{ mouseButton: MouseBindings.Primary }],
  226. });
  227. console.log(
  228. `[MeasurementToolManager] Angle tool activated for viewport: ${viewportId}`
  229. );
  230. return true;
  231. } catch (error) {
  232. console.error(
  233. `[MeasurementToolManager] Error activating angle tool:`,
  234. error
  235. );
  236. return false;
  237. }
  238. }
  239. /**
  240. * 停用角度测量工具
  241. */
  242. static deactivateAngleTool(viewportId: string): boolean {
  243. const toolGroup = this.getToolGroup(viewportId);
  244. if (!toolGroup) return false;
  245. try {
  246. toolGroup.setToolPassive(AngleTool.toolName, {
  247. removeAllBindings: true,
  248. });
  249. console.log(
  250. `[MeasurementToolManager] Angle tool deactivated for viewport: ${viewportId}`
  251. );
  252. return true;
  253. } catch (error) {
  254. console.error(
  255. `[MeasurementToolManager] Error deactivating angle tool:`,
  256. error
  257. );
  258. return false;
  259. }
  260. }
  261. /**
  262. * 检查角度测量工具是否处于激活状态
  263. */
  264. static isAngleToolActive(viewportId: string): boolean {
  265. const toolGroup = this.getToolGroup(viewportId);
  266. if (!toolGroup) return false;
  267. try {
  268. const activeTool = toolGroup.getActivePrimaryMouseButtonTool();
  269. return activeTool === AngleTool.toolName;
  270. } catch (error) {
  271. console.error(
  272. `[MeasurementToolManager] Error checking angle tool state:`,
  273. error
  274. );
  275. return false;
  276. }
  277. }
  278. /**
  279. * 切换角度测量工具状态
  280. */
  281. static toggleAngleTool(viewportId: string): boolean {
  282. const isActive = this.isAngleToolActive(viewportId);
  283. if (isActive) {
  284. return this.deactivateAngleTool(viewportId);
  285. } else {
  286. return this.activateAngleTool(viewportId);
  287. }
  288. }
  289. /**
  290. * 清除指定 viewport 的所有角度测量标注
  291. */
  292. static clearAngleMeasurements(viewportId: string): boolean {
  293. try {
  294. const viewport =
  295. cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
  296. if (!viewport) {
  297. console.warn(
  298. `[MeasurementToolManager] Viewport not found: ${viewportId}`
  299. );
  300. return false;
  301. }
  302. const annotations = cornerstoneTools.annotation.state.getAnnotations(
  303. AngleTool.toolName,
  304. viewport.element
  305. );
  306. let removedCount = 0;
  307. annotations.forEach((annotation) => {
  308. if (annotation.annotationUID) {
  309. cornerstoneTools.annotation.state.removeAnnotation(
  310. annotation.annotationUID
  311. );
  312. removedCount++;
  313. }
  314. });
  315. viewport.render();
  316. console.log(
  317. `[MeasurementToolManager] Cleared ${removedCount} angle measurements for viewport: ${viewportId}`
  318. );
  319. return true;
  320. } catch (error) {
  321. console.error(
  322. `[MeasurementToolManager] Error clearing angle measurements:`,
  323. error
  324. );
  325. return false;
  326. }
  327. }
  328. /**
  329. * 获取指定 viewport 的所有角度测量结果
  330. */
  331. // eslint-disable-next-line
  332. static getAngleMeasurements(viewportId: string): any[] {
  333. try {
  334. const viewport =
  335. cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
  336. if (!viewport) {
  337. console.warn(
  338. `[MeasurementToolManager] Viewport not found: ${viewportId}`
  339. );
  340. return [];
  341. }
  342. const annotations = cornerstoneTools.annotation.state.getAnnotations(
  343. AngleTool.toolName,
  344. viewport.element
  345. );
  346. return annotations.map((annotation) => ({
  347. annotationUID: annotation.annotationUID,
  348. angle: annotation.data?.cachedStats?.angle || 0,
  349. unit: annotation.data?.cachedStats?.unit || 'degrees',
  350. points: annotation.data?.handles?.points || [],
  351. }));
  352. } catch (error) {
  353. console.error(
  354. `[MeasurementToolManager] Error getting angle measurements:`,
  355. error
  356. );
  357. return [];
  358. }
  359. }
  360. /**
  361. * 为多个 viewport 批量激活角度测量工具
  362. */
  363. static activateAngleToolForViewports(viewportIds: string[]): boolean[] {
  364. return viewportIds.map((viewportId) => this.activateAngleTool(viewportId));
  365. }
  366. /**
  367. * 为多个 viewport 批量停用角度测量工具
  368. */
  369. static deactivateAngleToolForViewports(viewportIds: string[]): boolean[] {
  370. return viewportIds.map((viewportId) =>
  371. this.deactivateAngleTool(viewportId)
  372. );
  373. }
  374. /**
  375. * 为多个 viewport 批量清除角度测量
  376. */
  377. static clearAngleMeasurementsForViewports(viewportIds: string[]): boolean[] {
  378. return viewportIds.map((viewportId) =>
  379. this.clearAngleMeasurements(viewportId)
  380. );
  381. }
  382. // ==================== 胫骨平台夹角测量工具 ====================
  383. /**
  384. * 激活胫骨平台夹角测量工具
  385. */
  386. static activateTibialPlateauAngleTool(viewportId: string): boolean {
  387. const toolGroup = this.getToolGroup(viewportId);
  388. if (!toolGroup) return false;
  389. try {
  390. // 停用其他可能冲突的工具
  391. toolGroup.setToolPassive(WindowLevelTool.toolName, {
  392. removeAllBindings: true,
  393. });
  394. toolGroup.setToolPassive(MagnifyTool.toolName, {
  395. removeAllBindings: true,
  396. });
  397. toolGroup.setToolPassive(LengthTool.toolName, {
  398. removeAllBindings: true,
  399. });
  400. toolGroup.setToolPassive(AngleTool.toolName, {
  401. removeAllBindings: true,
  402. });
  403. // 激活胫骨平台夹角测量工具
  404. toolGroup.setToolActive(TibialPlateauAngleTool.toolName, {
  405. bindings: [{ mouseButton: MouseBindings.Primary }],
  406. });
  407. // 获取工具实例并激活修改模式
  408. const toolInstance = toolGroup.getToolInstance(
  409. TibialPlateauAngleTool.toolName
  410. ) as TibialPlateauAngleTool;
  411. // 使用参数中的 viewportId,而不是硬编码
  412. const viewport = cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
  413. if (toolInstance && viewport.element) {
  414. toolInstance._activateModify(viewport.element);
  415. }
  416. // 自动创建一个预设的注解
  417. try {
  418. if (viewport && viewport.element) {
  419. // 创建预设注解
  420. const defaultAnnotation = TibialPlateauAngleTool.createDefaultAnnotation(
  421. viewport.element as HTMLDivElement,
  422. viewport as cornerstone.Types.IStackViewport
  423. );
  424. // 添加注解到状态管理
  425. cornerstoneTools.annotation.state.addAnnotation(
  426. defaultAnnotation,
  427. viewport.element
  428. );
  429. // 获取工具实例并更新缓存统计数据
  430. const enabledElement = cornerstone.getEnabledElement(viewport.element);
  431. if (enabledElement) {
  432. const toolInstance = toolGroup.getToolInstance(
  433. TibialPlateauAngleTool.toolName
  434. ) as TibialPlateauAngleTool;
  435. if (toolInstance && '_updateCachedStats' in toolInstance) {
  436. (toolInstance as any)._updateCachedStats(defaultAnnotation, enabledElement);
  437. }
  438. }
  439. // 触发渲染更新
  440. // 触发渲染更新
  441. const viewportIdsToRender = cornerstoneTools.utilities.viewportFilters.getViewportIdsWithToolToRender(
  442. viewport.element,
  443. TibialPlateauAngleTool.toolName
  444. );
  445. cornerstoneTools.utilities.triggerAnnotationRenderForViewportIds(
  446. viewportIdsToRender
  447. );
  448. console.log('[MeasurementToolManager] Default TPA annotation created successfully');
  449. }
  450. } catch (error) {
  451. console.error('[MeasurementToolManager] Failed to create default annotation:', error);
  452. // 注解创建失败不影响工具激活
  453. }
  454. console.log(
  455. `[MeasurementToolManager] TibialPlateauAngle tool activated for viewport: ${viewportId}`
  456. );
  457. return true;
  458. } catch (error) {
  459. console.error(
  460. `[MeasurementToolManager] Error activating TibialPlateauAngle tool:`,
  461. error
  462. );
  463. return false;
  464. }
  465. }
  466. /**
  467. * 停用胫骨平台夹角测量工具
  468. */
  469. static deactivateTibialPlateauAngleTool(viewportId: string): boolean {
  470. const toolGroup = this.getToolGroup(viewportId);
  471. if (!toolGroup) return false;
  472. try {
  473. toolGroup.setToolPassive(TibialPlateauAngleTool.toolName, {
  474. removeAllBindings: true,
  475. });
  476. console.log(
  477. `[MeasurementToolManager] TibialPlateauAngle tool deactivated for viewport: ${viewportId}`
  478. );
  479. return true;
  480. } catch (error) {
  481. console.error(
  482. `[MeasurementToolManager] Error deactivating TibialPlateauAngle tool:`,
  483. error
  484. );
  485. return false;
  486. }
  487. }
  488. /**
  489. * 检查胫骨平台夹角测量工具是否处于激活状态
  490. */
  491. static isTibialPlateauAngleToolActive(viewportId: string): boolean {
  492. const toolGroup = this.getToolGroup(viewportId);
  493. if (!toolGroup) return false;
  494. try {
  495. const activeTool = toolGroup.getActivePrimaryMouseButtonTool();
  496. return activeTool === TibialPlateauAngleTool.toolName;
  497. } catch (error) {
  498. console.error(
  499. `[MeasurementToolManager] Error checking TibialPlateauAngle tool state:`,
  500. error
  501. );
  502. return false;
  503. }
  504. }
  505. /**
  506. * 切换胫骨平台夹角测量工具状态
  507. */
  508. static toggleTibialPlateauAngleTool(viewportId: string): boolean {
  509. const isActive = this.isTibialPlateauAngleToolActive(viewportId);
  510. if (isActive) {
  511. return this.deactivateTibialPlateauAngleTool(viewportId);
  512. } else {
  513. return this.activateTibialPlateauAngleTool(viewportId);
  514. }
  515. }
  516. /**
  517. * 清除指定 viewport 的所有胫骨平台夹角测量标注
  518. */
  519. static clearTibialPlateauAngleMeasurements(viewportId: string): boolean {
  520. try {
  521. const viewport =
  522. cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
  523. if (!viewport) {
  524. console.warn(
  525. `[MeasurementToolManager] Viewport not found: ${viewportId}`
  526. );
  527. return false;
  528. }
  529. const annotations = cornerstoneTools.annotation.state.getAnnotations(
  530. TibialPlateauAngleTool.toolName,
  531. viewport.element
  532. );
  533. let removedCount = 0;
  534. annotations.forEach((annotation) => {
  535. if (annotation.annotationUID) {
  536. cornerstoneTools.annotation.state.removeAnnotation(
  537. annotation.annotationUID
  538. );
  539. removedCount++;
  540. }
  541. });
  542. viewport.render();
  543. console.log(
  544. `[MeasurementToolManager] Cleared ${removedCount} TibialPlateauAngle measurements for viewport: ${viewportId}`
  545. );
  546. return true;
  547. } catch (error) {
  548. console.error(
  549. `[MeasurementToolManager] Error clearing TibialPlateauAngle measurements:`,
  550. error
  551. );
  552. return false;
  553. }
  554. }
  555. /**
  556. * 获取指定 viewport 的所有胫骨平台夹角测量结果
  557. */
  558. // eslint-disable-next-line
  559. static getTibialPlateauAngleMeasurements(viewportId: string): any[] {
  560. try {
  561. const viewport =
  562. cornerstone.getEnabledElementByViewportId(viewportId)?.viewport;
  563. if (!viewport) {
  564. console.warn(
  565. `[MeasurementToolManager] Viewport not found: ${viewportId}`
  566. );
  567. return [];
  568. }
  569. const annotations = cornerstoneTools.annotation.state.getAnnotations(
  570. TibialPlateauAngleTool.toolName,
  571. viewport.element
  572. );
  573. return annotations.map((annotation) => ({
  574. annotationUID: annotation.annotationUID,
  575. TPA: annotation.data?.cachedStats?.TPA || 0,
  576. unit: 'degrees',
  577. points: annotation.data?.handles?.points || [],
  578. }));
  579. } catch (error) {
  580. console.error(
  581. `[MeasurementToolManager] Error getting TibialPlateauAngle measurements:`,
  582. error
  583. );
  584. return [];
  585. }
  586. }
  587. /**
  588. * 为多个 viewport 批量激活胫骨平台夹角测量工具
  589. */
  590. static activateTibialPlateauAngleToolForViewports(
  591. viewportIds: string[]
  592. ): boolean[] {
  593. return viewportIds.map((viewportId) =>
  594. this.activateTibialPlateauAngleTool(viewportId)
  595. );
  596. }
  597. /**
  598. * 为多个 viewport 批量停用胫骨平台夹角测量工具
  599. */
  600. static deactivateTibialPlateauAngleToolForViewports(
  601. viewportIds: string[]
  602. ): boolean[] {
  603. return viewportIds.map((viewportId) =>
  604. this.deactivateTibialPlateauAngleTool(viewportId)
  605. );
  606. }
  607. /**
  608. * 为多个 viewport 批量清除胫骨平台夹角测量
  609. */
  610. static clearTibialPlateauAngleMeasurementsForViewports(
  611. viewportIds: string[]
  612. ): boolean[] {
  613. return viewportIds.map((viewportId) =>
  614. this.clearTibialPlateauAngleMeasurements(viewportId)
  615. );
  616. }
  617. }