imageActions.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. import axiosInstance from './interceptor';
  2. import { getApiBaseUrl } from './config';
  3. /**
  4. * 发送图像请求参数
  5. */
  6. export interface SendImageRequest {
  7. /** 图像实例 UID */
  8. sop_instance_uid: string;
  9. /** PACS 节点名称 */
  10. pacs_name: string;
  11. }
  12. /**
  13. * 存储响应数据
  14. */
  15. export interface StoreReplyData {
  16. /** 类型标识 */
  17. '@type': string;
  18. /** 是否成功 */
  19. ok: boolean;
  20. /** 输出信息 */
  21. output: string;
  22. }
  23. /**
  24. * 发送图像响应
  25. */
  26. export interface SendImageResponse {
  27. /** 响应码 */
  28. code: string;
  29. /** 描述信息 */
  30. description: string;
  31. /** 解决方案 */
  32. solution: string;
  33. /** 数据内容 */
  34. data: StoreReplyData;
  35. }
  36. /**
  37. * 发送图像到 PACS 节点
  38. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  39. * @param pacsName PACS 节点名称
  40. * @returns 发送结果,包含成功状态和输出信息
  41. * @throws 当发送失败时抛出错误
  42. *
  43. * @example
  44. * ```typescript
  45. * const result = await sendImageToPacs(
  46. * '1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671',
  47. * 'pacs1'
  48. * );
  49. * console.log('发送成功:', result.data.ok);
  50. * ```
  51. */
  52. export const sendImageToPacs = async (
  53. sopInstanceUid: string,
  54. pacsName: string
  55. ): Promise<SendImageResponse> => {
  56. try {
  57. const response = await axiosInstance.post<SendImageResponse>(
  58. '/auth/scp/store',
  59. {
  60. sop_instance_uid: sopInstanceUid,
  61. pacs_name: pacsName,
  62. }
  63. );
  64. if (response.data.code !== '0x000000') {
  65. throw new Error(`发送图像失败: ${response.data.description}`);
  66. }
  67. return response.data;
  68. } catch (error) {
  69. console.error('Error sending image to PACS:', error);
  70. throw error;
  71. }
  72. };
  73. /**
  74. * 按 Study 批量发送图像到 PACS 节点
  75. * @param studyId Study 实例 UID (Study Instance UID)
  76. * @param pacsName PACS 节点名称
  77. * @returns 发送结果,包含成功状态和输出信息
  78. * @throws 当发送失败时抛出错误
  79. *
  80. * @example
  81. * ```typescript
  82. * const result = await sendStudyToPacs(
  83. * '1.2.276.0.1000000.5.1.2.701601461.33458.1750833219.482097',
  84. * 'pacs1'
  85. * );
  86. * console.log('发送成功:', result.data.ok);
  87. * ```
  88. */
  89. export const sendStudyToPacs = async (
  90. studyId: string,
  91. pacsName: string
  92. ): Promise<SendImageResponse> => {
  93. try {
  94. const response = await axiosInstance.post<SendImageResponse>(
  95. '/auth/scp/study_store',
  96. {
  97. instance_uid: studyId,
  98. pacs_name: pacsName,
  99. }
  100. );
  101. if (response.data.code !== '0x000000') {
  102. throw new Error(`按 Study 发送图像失败: ${response.data.description}`);
  103. }
  104. return response.data;
  105. } catch (error) {
  106. console.error('Error sending study to PACS:', error);
  107. throw error;
  108. }
  109. };
  110. /**
  111. * 图像另存为响应类型
  112. */
  113. export interface SaveImageAsResponse {
  114. /** 响应码 */
  115. code: string;
  116. /** 描述信息 */
  117. description: string;
  118. /** 解决方案 */
  119. solution: string;
  120. /** 数据内容 */
  121. data: Record<string, never>;
  122. }
  123. /**
  124. * 图像另存为
  125. * 复制选中的图像到同一个 Study 中
  126. *
  127. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  128. * @returns 另存为结果
  129. * @throws 当另存为失败时抛出错误
  130. *
  131. * @example
  132. * ```typescript
  133. * await saveImageAs('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671');
  134. * ```
  135. */
  136. export const saveImageAs = async (
  137. sopInstanceUid: string
  138. ): Promise<SaveImageAsResponse> => {
  139. try {
  140. const response = await axiosInstance.post<SaveImageAsResponse>(
  141. '/auth/image/save_as',
  142. {
  143. instance_uid: sopInstanceUid,
  144. }
  145. );
  146. if (response.data.code !== '0x000000') {
  147. throw new Error(`图像另存为失败: ${response.data.description}`);
  148. }
  149. return response.data;
  150. } catch (error) {
  151. console.error('Error saving image as:', error);
  152. throw error;
  153. }
  154. };
  155. /**
  156. * 图像处理参数响应类型
  157. */
  158. export interface GetProcessingParamsResponse {
  159. /** 响应码 */
  160. code: string;
  161. /** 描述信息 */
  162. description: string;
  163. /** 解决方案 */
  164. solution: string;
  165. /** 数据内容 */
  166. data: {
  167. /** 增益 */
  168. contrast: number;
  169. /** 细节 */
  170. detail: number;
  171. /** 动态范围 */
  172. latitude: number;
  173. /** 噪声模式 */
  174. noise: number;
  175. /** 对比度系数 */
  176. ww_coef: number;
  177. /** 亮度系数 */
  178. wl_coef: number;
  179. };
  180. }
  181. /**
  182. * 获取当前图像处理参数
  183. * 完成图像处理准备工作,并返回增益、细节、动态范围、噪声模式当前值
  184. *
  185. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  186. * @returns 当前图像处理参数
  187. * @throws 当获取失败时抛出错误
  188. *
  189. * @example
  190. * ```typescript
  191. * const params = await getImageProcessingParams('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671');
  192. * console.log('当前增益:', params.data.contrast);
  193. * ```
  194. */
  195. export const getImageProcessingParams = async (
  196. sopInstanceUid: string
  197. ): Promise<GetProcessingParamsResponse> => {
  198. try {
  199. const response = await axiosInstance.get<GetProcessingParamsResponse>(
  200. `/auth/image/${sopInstanceUid}/preparation`
  201. );
  202. if (response.data.code !== '0x000000') {
  203. throw new Error(`获取图像处理参数失败: ${response.data.description}`);
  204. }
  205. return response.data;
  206. } catch (error) {
  207. console.error('Error getting image processing params:', error);
  208. throw error;
  209. }
  210. };
  211. /**
  212. * 保存图像处理参数请求类型
  213. */
  214. export interface SaveProcessingParamsRequest {
  215. /** 增益 */
  216. contrast: number;
  217. /** 细节 */
  218. detail: number;
  219. /** 动态范围 */
  220. latitude: number;
  221. /** 噪声模式 */
  222. noise: number;
  223. /** 对比度系数 */
  224. ww_coef: number;
  225. /** 亮度系数 */
  226. wl_coef: number;
  227. }
  228. /**
  229. * 获取处理的dcm
  230. */
  231. export interface GetProcessedDcmRequest {
  232. /** 增益 */
  233. contrast: string;
  234. /** 细节 */
  235. detail: string;
  236. /** 动态范围 */
  237. latitude: string;
  238. /** 噪声模式 */
  239. noise: string;
  240. /** 对比度系数 */
  241. ww_coef: string;
  242. /** 亮度系数 */
  243. wl_coef: string;
  244. }
  245. /**
  246. * 构建处理后的dcm URL(不发送请求)
  247. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  248. * @param params 图像处理参数
  249. * @returns 处理后的dcm URL字符串
  250. * @example
  251. * ```typescript
  252. * const url = buildProcessedDcmUrl('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', {
  253. * contrast: '5.0',
  254. * detail: '9.0',
  255. * latitude: '25.0',
  256. * noise: '12.0',
  257. * ww_coef: '1.0',
  258. * wl_coef: '0.0'
  259. * });
  260. * console.log('处理后图像URL:', url);
  261. * ```
  262. */
  263. export const buildProcessedDcmUrl = (
  264. sopInstanceUid: string,
  265. params: GetProcessedDcmRequest
  266. ): string => {
  267. const queryParams = new URLSearchParams({
  268. contrast: params.contrast,
  269. detail: params.detail,
  270. latitude: params.latitude,
  271. noise: params.noise,
  272. ww_coef: params.ww_coef,
  273. wl_coef: params.wl_coef,
  274. });
  275. const API_BASE_URL = getApiBaseUrl();
  276. return `${API_BASE_URL}pub/image/${sopInstanceUid}/proc?${queryParams.toString()}`;//暂时使用公开的访问方式,不需要token
  277. // return `${API_BASE_URL}auth/image/${sopInstanceUid}/proc?${queryParams.toString()}`;
  278. };
  279. /**
  280. * 保存图像处理参数响应类型
  281. */
  282. export interface SaveProcessingParamsResponse {
  283. /** 响应码 */
  284. code: string;
  285. /** 描述信息 */
  286. description: string;
  287. /** 解决方案 */
  288. solution: string;
  289. /** 数据内容 */
  290. data: Record<string, never>;
  291. }
  292. /**
  293. * 获取处理后的 dcm 文件
  294. * 实时获取应用指定参数后的 dcm 文件(用于预览)
  295. *
  296. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  297. * @param params 图像处理参数
  298. * @returns 处理后的 dcm 文件 Blob
  299. * @throws 当获取失败时抛出错误
  300. *
  301. * @example
  302. * ```typescript
  303. * const dcmBlob = await getProcessedDcm('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', {
  304. * contrast: '5.0',
  305. * detail: '9.0',
  306. * latitude: '25.0',
  307. * noise: '12.0',
  308. * ww_coef: '1.0',
  309. * wl_coef: '0.0'
  310. * });
  311. * const blobUrl = URL.createObjectURL(dcmBlob);
  312. * ```
  313. */
  314. export const getProcessedDcm = async (
  315. sopInstanceUid: string,
  316. params: GetProcessedDcmRequest
  317. ): Promise<Blob> => {
  318. try {
  319. const response = await axiosInstance.get(
  320. `/auth/image/${sopInstanceUid}/proc`,
  321. {
  322. params: {
  323. contrast: params.contrast,
  324. detail: params.detail,
  325. latitude: params.latitude,
  326. noise: params.noise,
  327. ww_coef: params.ww_coef,
  328. wl_coef: params.wl_coef,
  329. },
  330. responseType: 'blob', // 重要:指定响应类型为blob
  331. }
  332. );
  333. return response.data;
  334. } catch (error) {
  335. console.error('Error getting processed dcm:', error);
  336. throw error;
  337. }
  338. };
  339. /**
  340. * 保存图像处理参数
  341. * 保存应用增益、细节、动态范围、噪声模式、对比度系数、亮度系数后的 dcm
  342. *
  343. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  344. * @param params 图像处理参数
  345. * @returns 保存结果
  346. * @throws 当保存失败时抛出错误
  347. *
  348. * @example
  349. * ```typescript
  350. * await saveImageProcessingParams('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', {
  351. * contrast: 5.0,
  352. * detail: 9.0,
  353. * latitude: 25.0,
  354. * noise: 12.0,
  355. * ww_coef: 1.0,
  356. * wl_coef: 0.0
  357. * });
  358. * ```
  359. */
  360. export const saveImageProcessingParams = async (
  361. sopInstanceUid: string,
  362. params: SaveProcessingParamsRequest
  363. ): Promise<SaveProcessingParamsResponse> => {
  364. try {
  365. const response = await axiosInstance.post<SaveProcessingParamsResponse>(
  366. `/auth/image/${sopInstanceUid}/proc`,
  367. params
  368. );
  369. if (response.data.code !== '0x000000') {
  370. throw new Error(`保存图像处理参数失败: ${response.data.description}`);
  371. }
  372. return response.data;
  373. } catch (error) {
  374. console.error('Error saving image processing params:', error);
  375. throw error;
  376. }
  377. };
  378. /**
  379. * 保存窗宽窗位请求类型
  380. */
  381. export interface SaveWindowCenterWidthRequest {
  382. /** 窗位 */
  383. window_center: number;
  384. /** 窗宽 */
  385. window_width: number;
  386. }
  387. /**
  388. * 保存窗宽窗位响应类型
  389. */
  390. export interface SaveWindowCenterWidthResponse {
  391. /** 响应码 */
  392. code: string;
  393. /** 描述信息 */
  394. description: string;
  395. /** 解决方案 */
  396. solution: string;
  397. /** 数据内容 */
  398. data: Record<string, never>;
  399. }
  400. /**
  401. * 获取当前图像处理参数
  402. * 完成图像处理准备工作,并返回增益、细节、动态范围、噪声模式、对比度系数、亮度系数当前值
  403. *
  404. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  405. * @returns 当前图像处理参数
  406. * @throws 当获取失败时抛出错误
  407. *
  408. * @example
  409. * ```typescript
  410. * const params = await getImageProcessingParams('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671');
  411. * console.log('当前增益:', params.data.contrast);
  412. * console.log('当前对比度系数:', params.data.ww_coef);
  413. * ```
  414. */
  415. export const saveWindowCenterWidth = async (
  416. sopInstanceUid: string,
  417. windowCenter: number,
  418. windowWidth: number
  419. ): Promise<SaveWindowCenterWidthResponse> => {
  420. try {
  421. const response = await axiosInstance.post<SaveWindowCenterWidthResponse>(
  422. `/auth/image/${sopInstanceUid}/wcww`,
  423. {
  424. window_center: windowCenter,
  425. window_width: windowWidth,
  426. }
  427. );
  428. if (response.data.code !== '0x000000') {
  429. throw new Error(`保存窗宽窗位失败: ${response.data.description}`);
  430. }
  431. return response.data;
  432. } catch (error) {
  433. console.error('Error saving window center/width:', error);
  434. throw error;
  435. }
  436. };
  437. // =============================================================================
  438. // 图像处理v2 API
  439. // =============================================================================
  440. /**
  441. * 获取tiff图像响应类型
  442. */
  443. export interface GetTiffResponse {
  444. /** 响应码 */
  445. code: string;
  446. /** 描述信息 */
  447. description: string;
  448. /** 解决方案 */
  449. solution: string;
  450. /** 文件数据 */
  451. data: File;
  452. }
  453. /**
  454. * 获取tiff图像(公开接口)
  455. *
  456. * @param filename tiff文件名
  457. * @returns tiff文件 Blob
  458. * @throws 当获取失败时抛出错误
  459. *
  460. * @example
  461. * ```typescript
  462. * const tiffBlob = await getTiffImagePublic('1.2.276.0.1000000.5.1.4.dcm.tiff');
  463. * const blobUrl = URL.createObjectURL(tiffBlob);
  464. * ```
  465. */
  466. export const getTiffImagePublic = async (
  467. filename: string
  468. ): Promise<Blob> => {
  469. try {
  470. const response = await axiosInstance.get(
  471. `/pub/tif/${filename}`,
  472. {
  473. responseType: 'blob', // 重要:指定响应类型为blob
  474. }
  475. );
  476. return response.data;
  477. } catch (error) {
  478. console.error('Error getting tiff image (public):', error);
  479. throw error;
  480. }
  481. };
  482. /**
  483. * 获取tiff图像(认证接口)
  484. *
  485. * @param filename tiff文件名
  486. * @returns tiff文件 Blob
  487. * @throws 当获取失败时抛出错误
  488. *
  489. * @example
  490. * ```typescript
  491. * const tiffBlob = await getTiffImageAuth('1.2.276.0.1000000.5.1.4.dcm.tiff');
  492. * const blobUrl = URL.createObjectURL(tiffBlob);
  493. * ```
  494. */
  495. export const getTiffImageAuth = async (
  496. filename: string
  497. ): Promise<Blob> => {
  498. try {
  499. const response = await axiosInstance.get(
  500. `/auth/f/tif/${filename}`,
  501. {
  502. responseType: 'blob', // 重要:指定响应类型为blob
  503. }
  504. );
  505. return response.data;
  506. } catch (error) {
  507. console.error('Error getting tiff image (auth):', error);
  508. throw error;
  509. }
  510. };
  511. /**
  512. * 图像处理参数v2响应类型
  513. */
  514. export interface GetImageProcessingParamsV2Response {
  515. /** 响应码 */
  516. code: string;
  517. /** 描述信息 */
  518. description: string;
  519. /** 解决方案 */
  520. solution: string;
  521. /** 数据内容 */
  522. data: {
  523. /** 增益 */
  524. contrast: number;
  525. /** 细节 */
  526. detail: number;
  527. /** 动态范围 */
  528. latitude: number;
  529. /** 噪声模式 */
  530. noise: number;
  531. /** 对比度 */
  532. ww_coef: number;
  533. /** 亮度 */
  534. wl_coef: number;
  535. };
  536. }
  537. /**
  538. * 获取当前dcm所用的图像处理参数(v2)
  539. *
  540. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  541. * @returns 当前图像处理参数
  542. * @throws 当获取失败时抛出错误
  543. *
  544. * @example
  545. * ```typescript
  546. * const params = await getImageProcessingParamsV2('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671');
  547. * console.log('当前增益:', params.data.contrast);
  548. * console.log('当前对比度:', params.data.ww_coef);
  549. * ```
  550. */
  551. export const getImageProcessingParamsV2 = async (
  552. sopInstanceUid: string
  553. ): Promise<GetImageProcessingParamsV2Response> => {
  554. try {
  555. const response = await axiosInstance.get<GetImageProcessingParamsV2Response>(
  556. `/auth/image/${sopInstanceUid}/img_proc_params`
  557. );
  558. if (response.data.code !== '0x000000') {
  559. throw new Error(`获取图像处理参数失败: ${response.data.description}`);
  560. }
  561. return response.data;
  562. } catch (error) {
  563. console.error('Error getting image processing params v2:', error);
  564. throw error;
  565. }
  566. };
  567. /**
  568. * 重建dcm请求类型
  569. */
  570. export interface RebuildDcmRequest {
  571. /** 增益 */
  572. contrast: number;
  573. /** 细节 */
  574. detail: number;
  575. /** 动态范围 */
  576. latitude: number;
  577. /** 噪声模式 */
  578. noise: number;
  579. /** 对比度 */
  580. ww_coef: number;
  581. /** 亮度 */
  582. wl_coef: number;
  583. }
  584. /**
  585. * 重建dcm响应类型
  586. */
  587. export interface RebuildDcmResponse {
  588. /** 响应码 */
  589. code: string;
  590. /** 描述信息 */
  591. description: string;
  592. /** 解决方案 */
  593. solution: string;
  594. /** 数据内容 */
  595. data: Record<string, never>;
  596. }
  597. /**
  598. * 重建dcm(保存应用增强参数后的dcm)
  599. *
  600. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  601. * @param params 图像处理参数
  602. * @returns 重建结果
  603. * @throws 当重建失败时抛出错误
  604. *
  605. * @example
  606. * ```typescript
  607. * await rebuildDcm('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', {
  608. * contrast: 5.0,
  609. * detail: 8.0,
  610. * latitude: 6.0,
  611. * noise: 4.0,
  612. * ww_coef: 1.2,
  613. * wl_coef: -0.5
  614. * });
  615. * ```
  616. */
  617. export const rebuildDcm = async (
  618. sopInstanceUid: string,
  619. params: RebuildDcmRequest
  620. ): Promise<RebuildDcmResponse> => {
  621. try {
  622. const response = await axiosInstance.post<RebuildDcmResponse>(
  623. `/auth/image/${sopInstanceUid}/rebuild_dcm`,
  624. params
  625. );
  626. if (response.data.code !== '0x000000') {
  627. throw new Error(`重建dcm失败: ${response.data.description}`);
  628. }
  629. return response.data;
  630. } catch (error) {
  631. console.error('Error rebuilding dcm:', error);
  632. throw error;
  633. }
  634. };