AnnotationManager.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. // src/features/imageAnnotation/services/AnnotationManager.ts
  2. // 全局注释管理器 - 负责监听Cornerstone事件并管理注释的保存/加载
  3. import { eventTarget, getRenderingEngines } from '@cornerstonejs/core';
  4. import { EVENTS } from '@cornerstonejs/core';
  5. import { annotation as corAnnotation, Enums } from '@cornerstonejs/tools';
  6. import { AnnotationSerializer } from './AnnotationSerializer';
  7. import { AnnotationValidator } from './AnnotationValidator';
  8. import { annotationAPI } from '../../../API/annotation';
  9. import { extractSopInstanceUid } from '../../../utils/dicomUtils';
  10. import type { AnnotationData, AnnotationEventType } from '../types/annotation';
  11. import { IRenderingEngine, IStackViewport } from '@cornerstonejs/core/dist/esm/types';
  12. export class AnnotationManager {
  13. private eventListenersRegistered: boolean = false;
  14. private activeImageId: string | null = null;
  15. private serializer: AnnotationSerializer;
  16. private validator: AnnotationValidator;
  17. private pendingChanges: Map<string, string> = new Map();
  18. private saveTimeout: NodeJS.Timeout | null = null;
  19. private isLoading: boolean = false;
  20. constructor() {
  21. this.serializer = new AnnotationSerializer();
  22. this.validator = new AnnotationValidator();
  23. }
  24. async initialize(): Promise<void> {
  25. console.log('【annotationmanager】🔧 AnnotationManager 初始化中...');
  26. // 注册全局事件监听器
  27. this.registerGlobalEventListeners();
  28. console.log('【annotationmanager】🎧 全局注释事件监听器已设置');
  29. }
  30. async cleanup(): Promise<void> {
  31. console.log('【annotationmanager】🧹 AnnotationManager 清理中...');
  32. // 取消待处理的保存操作
  33. if (this.saveTimeout) {
  34. clearTimeout(this.saveTimeout);
  35. this.saveTimeout = null;
  36. }
  37. // 移除全局事件监听器
  38. this.unregisterGlobalEventListeners();
  39. console.log('【annotationmanager】✅ AnnotationManager 清理完成');
  40. }
  41. // ============ 事件监听器注册 ============
  42. private registerGlobalEventListeners(): void {
  43. if (this.eventListenersRegistered) {
  44. console.warn('【annotationmanager】事件监听器已注册,跳过重复注册');
  45. return;
  46. }
  47. console.log('【annotationmanager】注册事件监听器...');
  48. // 图像加载完成事件
  49. eventTarget.addEventListener(
  50. EVENTS.IMAGE_LOADED,
  51. this.handleImageLoadCompleted
  52. );
  53. // 图像渲染完成事件
  54. eventTarget.addEventListener(
  55. EVENTS.IMAGE_RENDERED,
  56. this.handleImageRendered
  57. );
  58. // 注释完成事件
  59. eventTarget.addEventListener(
  60. Enums.Events.ANNOTATION_COMPLETED,
  61. this.handleAnnotationCompleted
  62. );
  63. // 注释添加事件
  64. eventTarget.addEventListener(
  65. Enums.Events.ANNOTATION_ADDED,
  66. this.handleAnnotationAdded
  67. );
  68. // 注释修改事件
  69. eventTarget.addEventListener(
  70. Enums.Events.ANNOTATION_MODIFIED,
  71. this.handleAnnotationModified
  72. );
  73. // 注释删除事件
  74. eventTarget.addEventListener(
  75. Enums.Events.ANNOTATION_REMOVED,
  76. this.handleAnnotationRemoved
  77. );
  78. this.eventListenersRegistered = true;
  79. console.log('【annotationmanager】✅ 事件监听器注册完成');
  80. }
  81. private unregisterGlobalEventListeners(): void {
  82. if (!this.eventListenersRegistered) {
  83. return;
  84. }
  85. console.log('【annotationmanager】移除事件监听器...');
  86. eventTarget.removeEventListener(
  87. EVENTS.IMAGE_LOADED,
  88. this.handleImageLoadCompleted
  89. );
  90. eventTarget.removeEventListener(
  91. EVENTS.IMAGE_RENDERED,
  92. this.handleImageRendered
  93. );
  94. eventTarget.removeEventListener(
  95. Enums.Events.ANNOTATION_COMPLETED,
  96. this.handleAnnotationCompleted
  97. );
  98. eventTarget.removeEventListener(
  99. Enums.Events.ANNOTATION_ADDED,
  100. this.handleAnnotationCompleted
  101. );
  102. eventTarget.removeEventListener(
  103. Enums.Events.ANNOTATION_MODIFIED,
  104. this.handleAnnotationModified
  105. );
  106. eventTarget.removeEventListener(
  107. Enums.Events.ANNOTATION_REMOVED,
  108. this.handleAnnotationRemoved
  109. );
  110. this.eventListenersRegistered = false;
  111. console.log('【annotationmanager】✅ 事件监听器移除完成');
  112. }
  113. // ============ 事件处理方法 ============
  114. private handleImageLoadCompleted = (evt: any) => {
  115. const rawImageId = evt.detail.image?.imageId || evt.detail.imageId;
  116. console.log(`【annotationmanager】📸 原始图像ID: ${rawImageId}`);
  117. // 提取SOP Instance UID
  118. const sopInstanceUid = extractSopInstanceUid(rawImageId);
  119. if (!sopInstanceUid) {
  120. console.warn('【annotationmanager】无法从图像URL中提取SOP Instance UID:', rawImageId);
  121. return;
  122. }
  123. console.log(`【annotationmanager】📸 提取的SOP Instance UID: ${sopInstanceUid}`);
  124. // 使用提取的SOP Instance UID加载注释
  125. this.loadAnnotationsForImage(sopInstanceUid);
  126. };
  127. private handleImageRendered = (evt: any) => {
  128. const { viewportId } = evt.detail;
  129. console.log(`【annotationmanager】🎨 图像渲染完成: ${viewportId}`);
  130. // TODO: 确保注释正确显示
  131. };
  132. private handleAnnotationCompleted = (evt: any) => {
  133. console.log('🎯 监听到注释完成事件:', evt);
  134. console.log('📋 事件详情:', evt.detail);
  135. const { annotation, toolName } = evt.detail;
  136. console.log(`【annotationmanager】✏️ 注释创建完成: ${toolName}`);
  137. // 确保有活动的图像ID
  138. if (!this.activeImageId) {
  139. console.warn('【annotationmanager】没有活动的图像ID,跳过注释保存');
  140. return;
  141. }
  142. // 实现注释保存逻辑
  143. this.saveAnnotation(annotation, toolName);
  144. };
  145. private handleAnnotationAdded = (evt: any) => {
  146. console.log('【annotationmanager】🎯 监听到注释添加事件:', evt);
  147. console.log('【annotationmanager】📋 事件详情:', evt.detail);
  148. const { annotation } = evt.detail;
  149. const toolName = annotation.metadata?.toolName;
  150. console.log(`【annotationmanager】➕ 注释添加: ${toolName}`);
  151. // 确保有活动的图像ID
  152. if (!this.activeImageId) {
  153. console.warn('【annotationmanager】没有活动的图像ID,跳过注释保存');
  154. return;
  155. }
  156. // 实现注释保存逻辑
  157. this.saveAnnotation(annotation, toolName);
  158. };
  159. private handleAnnotationModified = (evt: any) => {
  160. console.log('🎯 监听到注释修改事件:', evt);
  161. console.log('📋 事件详情:', evt.detail);
  162. const { annotation, toolName } = evt.detail;
  163. console.log(`【annotationmanager】🔄 注释修改完成: ${toolName}`);
  164. // 确保有活动的图像ID
  165. if (!this.activeImageId) {
  166. console.warn('【annotationmanager】没有活动的图像ID,跳过注释更新');
  167. return;
  168. }
  169. // 实现注释更新逻辑
  170. this.updateAnnotation(annotation, toolName);
  171. };
  172. private handleAnnotationRemoved = (evt: any) => {
  173. console.log('🎯 监听到注释删除事件:', evt);
  174. console.log('📋 事件详情:', evt.detail);
  175. const { annotation } = evt.detail;
  176. const annotationUID = annotation.annotationUID;
  177. console.log(`【annotationmanager】🗑️ 注释删除完成: ${annotationUID}`);
  178. // 确保有活动的图像ID
  179. if (!this.activeImageId) {
  180. console.warn('【annotationmanager】没有活动的图像ID,跳过注释删除');
  181. return;
  182. }
  183. // 实现注释删除逻辑
  184. this.deleteAnnotation(annotation);
  185. };
  186. // ============ 注释操作方法 ============
  187. private async loadAnnotationsForImage(imageId: string): Promise<void> {
  188. if (this.isLoading) {
  189. console.log(`【annotationmanager】注释正在加载中,跳过: ${imageId}`);
  190. return;
  191. }
  192. this.isLoading = true;
  193. this.activeImageId = imageId;
  194. try {
  195. console.log(`【annotationmanager】📥 加载注释数据: ${imageId}`);
  196. // 调用API获取注释数据
  197. const response = await annotationAPI.getAnnotations(imageId);
  198. if (response.data && Array.isArray(response.data)) {
  199. const annotationStrings = response.data;
  200. // 验证注释数据
  201. // this.validator.validateAnnotationArray(annotationStrings);
  202. // 反序列化并渲染注释
  203. await this.renderAnnotations(annotationStrings);
  204. console.log(`【annotationmanager】✅ 注释加载完成: ${imageId} (${annotationStrings.length} 个注释)`);
  205. } else {
  206. console.log(`【annotationmanager】ℹ️ 图像 ${imageId} 没有注释数据`);
  207. }
  208. } catch (error) {
  209. console.error(`【annotationmanager】❌ 注释加载失败 ${imageId}:`, error);
  210. // 不抛出错误,让应用继续运行
  211. } finally {
  212. this.isLoading = false;
  213. }
  214. }
  215. private async saveAnnotation(annotation: any, toolName: string): Promise<void> {
  216. try {
  217. console.log(`【annotationmanager】💾 保存注释: ${toolName}`);
  218. // 序列化注释数据
  219. const serializedData = this.serializer.serialize(annotation, toolName);
  220. // 验证序列化数据
  221. // this.validator.validateAnnotationData(serializedData);
  222. // 添加到待保存队列(延迟保存)
  223. this.scheduleSave(serializedData);
  224. console.log(`【annotationmanager】✅ 注释保存任务已安排: ${toolName}`);
  225. } catch (error) {
  226. console.error(`【annotationmanager】❌ 注释保存失败 ${toolName}:`, error);
  227. }
  228. }
  229. private async updateAnnotation(annotation: any, toolName: string): Promise<void> {
  230. try {
  231. console.log(`【annotationmanager】🔄 更新注释: ${toolName}`);
  232. // 复用保存逻辑
  233. await this.saveAnnotation(annotation, toolName);
  234. console.log(`【annotationmanager】✅ 注释更新完成: ${toolName}`);
  235. } catch (error) {
  236. console.error(`【annotationmanager】❌ 注释更新失败 ${toolName}:`, error);
  237. }
  238. }
  239. private async deleteAnnotation(annotation: any): Promise<void> {
  240. try {
  241. const annotationUID = annotation.annotationUID;
  242. console.log(`【annotationmanager】🗑️ 删除注释: ${annotationUID}`);
  243. if (!this.activeImageId) {
  244. console.warn('【annotationmanager】没有活动的图像ID,跳过删除操作');
  245. return;
  246. }
  247. // 从 annotation 中获取 FrameOfReferenceUID
  248. const forUID = annotation.metadata?.referencedImageId;
  249. if (!forUID) {
  250. console.warn('【annotationmanager】FrameOfReferenceUID 未找到,跳过删除操作');
  251. return;
  252. }
  253. // 用 FrameOfReferenceUID 查找相关的 elements
  254. const elements = this.findElementsByFOR(forUID);
  255. // 收集删除后的所有注释
  256. const allAnnotationStrings: string[] = [];
  257. elements.forEach(element => {
  258. const anns = corAnnotation.state.getAnnotations('', element);
  259. if (anns) {
  260. // 序列化所有注释
  261. for (const [toolName, ann] of Object.entries(anns as any)) {
  262. try {
  263. const serialized = this.serializer.serialize(ann, toolName);
  264. allAnnotationStrings.push(serialized);
  265. } catch (error) {
  266. console.error('序列化注释失败:', error);
  267. // 跳过序列化失败的注释
  268. }
  269. }
  270. }
  271. });
  272. // 保存所有注释
  273. await annotationAPI.saveAnnotations(this.activeImageId, allAnnotationStrings);
  274. console.log(`【annotationmanager】✅ 注释删除完成: ${annotationUID} (剩余 ${allAnnotationStrings.length} 个注释)`);
  275. } catch (error) {
  276. console.error(`【annotationmanager】❌ 注释删除失败:`, error);
  277. }
  278. }
  279. // 用 FrameOfReferenceUID 查找 viewport elements
  280. private findElementsByFOR(imageId: string): any[] {
  281. const elements: any[] = [];
  282. if (!!getRenderingEngines) {
  283. const renderingEngines = getRenderingEngines();
  284. renderingEngines?.forEach((re: IRenderingEngine) => {
  285. re.getViewports().forEach((vp: IStackViewport) => {
  286. if (vp && vp.hasImageId(imageId) === true && vp.element) {
  287. elements.push(vp.element);
  288. }
  289. });
  290. });
  291. } else {
  292. console.warn('【annotationmanager】getRenderingEngines 方法不可用,无法查找元素');
  293. }
  294. return elements;
  295. }
  296. private async renderAnnotations(annotationStrings: string[]): Promise<void> {
  297. console.log(`【annotationmanager】🎨 渲染 ${annotationStrings.length} 个注释`);
  298. for (const annotationString of annotationStrings) {
  299. try {
  300. // 反序列化为Cornerstone格式
  301. const deserializedAnnotation = this.serializer.deserialize(annotationString);
  302. // 添加到Cornerstone注释状态
  303. corAnnotation.state.addAnnotation(deserializedAnnotation, deserializedAnnotation.metadata?.toolName || 'UnknownTool');
  304. console.log(`【annotationmanager】✅ 注释渲染成功`);
  305. } catch (error) {
  306. console.error(`【annotationmanager】❌ 注释渲染失败:`, error);
  307. // 继续处理其他注释
  308. }
  309. }
  310. }
  311. // ============ 延迟保存机制 ============
  312. /**
  313. * 安排延迟保存
  314. */
  315. private scheduleSave(annotationString: string): void {
  316. try {
  317. // 从JSON字符串中提取ID作为key
  318. const annotation = JSON.parse(annotationString);
  319. const key = annotation.annotationUID || annotation.id || `temp_${Date.now()}`;
  320. // 添加到待保存队列
  321. this.pendingChanges.set(key, annotationString);
  322. // 取消之前的延迟保存
  323. if (this.saveTimeout) {
  324. clearTimeout(this.saveTimeout);
  325. }
  326. // 设置新的延迟保存(1秒后)
  327. this.saveTimeout = setTimeout(() => {
  328. this.performBulkSave();
  329. }, 1000);
  330. } catch (error) {
  331. console.error('安排保存时解析注释失败:', error);
  332. }
  333. }
  334. /**
  335. * 执行批量保存
  336. */
  337. private async performBulkSave(): Promise<void> {
  338. if (this.pendingChanges.size === 0 || !this.activeImageId) {
  339. return;
  340. }
  341. const changes = Array.from(this.pendingChanges.values());
  342. this.pendingChanges.clear();
  343. try {
  344. console.log(`【annotationmanager】💾 批量保存 ${changes.length} 个注释`);
  345. // 调用API保存注释
  346. await annotationAPI.saveAnnotations(this.activeImageId, changes);
  347. console.log(`【annotationmanager】✅ 批量保存成功: ${changes.length} 个注释`);
  348. } catch (error) {
  349. console.error(`【annotationmanager】❌ 批量保存失败:`, error);
  350. // 保存失败时,重新加入待保存队列
  351. changes.forEach(change => {
  352. try {
  353. const annotation = JSON.parse(change);
  354. const key = annotation.annotationUID || annotation.id || `temp_${Date.now()}`;
  355. this.pendingChanges.set(key, change);
  356. } catch (error) {
  357. console.error('重新加入待保存队列时解析失败:', error);
  358. }
  359. });
  360. // 可以在这里实现重试逻辑
  361. }
  362. }
  363. }