imageActions.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. import axiosInstance from './interceptor';
  2. import { API_BASE_URL } 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. }
  177. /**
  178. * 获取当前图像处理参数
  179. * 完成图像处理准备工作,并返回增益、细节、动态范围、噪声模式当前值
  180. *
  181. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  182. * @returns 当前图像处理参数
  183. * @throws 当获取失败时抛出错误
  184. *
  185. * @example
  186. * ```typescript
  187. * const params = await getImageProcessingParams('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671');
  188. * console.log('当前增益:', params.data.contrast);
  189. * ```
  190. */
  191. export const getImageProcessingParams = async (
  192. sopInstanceUid: string
  193. ): Promise<GetProcessingParamsResponse> => {
  194. try {
  195. const response = await axiosInstance.get<GetProcessingParamsResponse>(
  196. `/auth/image/${sopInstanceUid}/preparation`
  197. );
  198. if (response.data.code !== '0x000000') {
  199. throw new Error(`获取图像处理参数失败: ${response.data.description}`);
  200. }
  201. return response.data;
  202. } catch (error) {
  203. console.error('Error getting image processing params:', error);
  204. throw error;
  205. }
  206. };
  207. /**
  208. * 保存图像处理参数请求类型
  209. */
  210. export interface SaveProcessingParamsRequest {
  211. /** 增益 */
  212. contrast: number;
  213. /** 细节 */
  214. detail: number;
  215. /** 动态范围 */
  216. latitude: number;
  217. /** 噪声模式 */
  218. noise: number;
  219. }
  220. /**
  221. * 获取处理的dcm
  222. */
  223. export interface GetProcessedDcmRequest {
  224. /** 增益 */
  225. contrast: string;
  226. /** 细节 */
  227. detail: string;
  228. /** 动态范围 */
  229. latitude: string;
  230. /** 噪声模式 */
  231. noise: string;
  232. }
  233. /**
  234. * 构建处理后的dcm URL(不发送请求)
  235. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  236. * @param params 图像处理参数
  237. * @returns 处理后的dcm URL字符串
  238. * @example
  239. * ```typescript
  240. * const url = buildProcessedDcmUrl('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', {
  241. * contrast: '5.0',
  242. * detail: '9.0',
  243. * latitude: '25.0',
  244. * noise: '12.0'
  245. * });
  246. * console.log('处理后图像URL:', url);
  247. * ```
  248. */
  249. export const buildProcessedDcmUrl = (
  250. sopInstanceUid: string,
  251. params: GetProcessedDcmRequest
  252. ): string => {
  253. const queryParams = new URLSearchParams({
  254. contrast: params.contrast,
  255. detail: params.detail,
  256. latitude: params.latitude,
  257. noise: params.noise,
  258. });
  259. return `${API_BASE_URL}pub/image/${sopInstanceUid}/proc?${queryParams.toString()}`;//暂时使用公开的访问方式,不需要token
  260. // return `${API_BASE_URL}auth/image/${sopInstanceUid}/proc?${queryParams.toString()}`;
  261. };
  262. /**
  263. * 保存图像处理参数响应类型
  264. */
  265. export interface SaveProcessingParamsResponse {
  266. /** 响应码 */
  267. code: string;
  268. /** 描述信息 */
  269. description: string;
  270. /** 解决方案 */
  271. solution: string;
  272. /** 数据内容 */
  273. data: Record<string, never>;
  274. }
  275. /**
  276. * 获取处理后的 dcm 文件
  277. * 实时获取应用指定参数后的 dcm 文件(用于预览)
  278. *
  279. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  280. * @param params 图像处理参数
  281. * @returns 处理后的 dcm 文件 Blob
  282. * @throws 当获取失败时抛出错误
  283. *
  284. * @example
  285. * ```typescript
  286. * const dcmBlob = await getProcessedDcm('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', {
  287. * contrast: 5.0,
  288. * detail: 9.0,
  289. * latitude: 25.0,
  290. * noise: 12.0
  291. * });
  292. * const blobUrl = URL.createObjectURL(dcmBlob);
  293. * ```
  294. */
  295. export const getProcessedDcm = async (
  296. sopInstanceUid: string,
  297. params: GetProcessedDcmRequest
  298. ): Promise<Blob> => {
  299. try {
  300. const response = await axiosInstance.get(
  301. `/auth/image/${sopInstanceUid}/proc`,
  302. {
  303. params: {
  304. contrast: params.contrast,
  305. detail: params.detail,
  306. latitude: params.latitude,
  307. noise: params.noise,
  308. },
  309. responseType: 'blob', // 重要:指定响应类型为blob
  310. }
  311. );
  312. return response.data;
  313. } catch (error) {
  314. console.error('Error getting processed dcm:', error);
  315. throw error;
  316. }
  317. };
  318. /**
  319. * 保存图像处理参数
  320. * 保存应用增益、细节、动态范围、噪声模式后的 dcm
  321. *
  322. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  323. * @param params 图像处理参数
  324. * @returns 保存结果
  325. * @throws 当保存失败时抛出错误
  326. *
  327. * @example
  328. * ```typescript
  329. * await saveImageProcessingParams('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', {
  330. * contrast: 5.0,
  331. * detail: 9.0,
  332. * latitude: 25.0,
  333. * noise: 12.0
  334. * });
  335. * ```
  336. */
  337. export const saveImageProcessingParams = async (
  338. sopInstanceUid: string,
  339. params: SaveProcessingParamsRequest
  340. ): Promise<SaveProcessingParamsResponse> => {
  341. try {
  342. const response = await axiosInstance.post<SaveProcessingParamsResponse>(
  343. `/auth/image/${sopInstanceUid}/proc`,
  344. params
  345. );
  346. if (response.data.code !== '0x000000') {
  347. throw new Error(`保存图像处理参数失败: ${response.data.description}`);
  348. }
  349. return response.data;
  350. } catch (error) {
  351. console.error('Error saving image processing params:', error);
  352. throw error;
  353. }
  354. };
  355. /**
  356. * 保存窗宽窗位请求类型
  357. */
  358. export interface SaveWindowCenterWidthRequest {
  359. /** 窗位 */
  360. window_center: number;
  361. /** 窗宽 */
  362. window_width: number;
  363. }
  364. /**
  365. * 保存窗宽窗位响应类型
  366. */
  367. export interface SaveWindowCenterWidthResponse {
  368. /** 响应码 */
  369. code: string;
  370. /** 描述信息 */
  371. description: string;
  372. /** 解决方案 */
  373. solution: string;
  374. /** 数据内容 */
  375. data: Record<string, never>;
  376. }
  377. /**
  378. * 保存窗位到 dcm
  379. *
  380. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  381. * @param windowCenter 窗位
  382. * @param windowWidth 窗宽
  383. * @returns 保存结果
  384. * @throws 当保存失败时抛出错误
  385. *
  386. * @example
  387. * ```typescript
  388. * await saveWindowCenterWidth('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', 50, 100);
  389. * ```
  390. */
  391. export const saveWindowCenterWidth = async (
  392. sopInstanceUid: string,
  393. windowCenter: number,
  394. windowWidth: number
  395. ): Promise<SaveWindowCenterWidthResponse> => {
  396. try {
  397. const response = await axiosInstance.post<SaveWindowCenterWidthResponse>(
  398. `/auth/image/${sopInstanceUid}/wcww`,
  399. {
  400. window_center: windowCenter,
  401. window_width: windowWidth,
  402. }
  403. );
  404. if (response.data.code !== '0x000000') {
  405. throw new Error(`保存窗宽窗位失败: ${response.data.description}`);
  406. }
  407. return response.data;
  408. } catch (error) {
  409. console.error('Error saving window center/width:', error);
  410. throw error;
  411. }
  412. };
  413. // =============================================================================
  414. // 图像处理v2 API
  415. // =============================================================================
  416. /**
  417. * 获取tiff图像响应类型
  418. */
  419. export interface GetTiffResponse {
  420. /** 响应码 */
  421. code: string;
  422. /** 描述信息 */
  423. description: string;
  424. /** 解决方案 */
  425. solution: string;
  426. /** 文件数据 */
  427. data: File;
  428. }
  429. /**
  430. * 获取tiff图像(公开接口)
  431. *
  432. * @param filename tiff文件名
  433. * @returns tiff文件 Blob
  434. * @throws 当获取失败时抛出错误
  435. *
  436. * @example
  437. * ```typescript
  438. * const tiffBlob = await getTiffImagePublic('1.2.276.0.1000000.5.1.4.dcm.tiff');
  439. * const blobUrl = URL.createObjectURL(tiffBlob);
  440. * ```
  441. */
  442. export const getTiffImagePublic = async (
  443. filename: string
  444. ): Promise<Blob> => {
  445. try {
  446. const response = await axiosInstance.get(
  447. `/pub/tif/${filename}`,
  448. {
  449. responseType: 'blob', // 重要:指定响应类型为blob
  450. }
  451. );
  452. return response.data;
  453. } catch (error) {
  454. console.error('Error getting tiff image (public):', error);
  455. throw error;
  456. }
  457. };
  458. /**
  459. * 获取tiff图像(认证接口)
  460. *
  461. * @param filename tiff文件名
  462. * @returns tiff文件 Blob
  463. * @throws 当获取失败时抛出错误
  464. *
  465. * @example
  466. * ```typescript
  467. * const tiffBlob = await getTiffImageAuth('1.2.276.0.1000000.5.1.4.dcm.tiff');
  468. * const blobUrl = URL.createObjectURL(tiffBlob);
  469. * ```
  470. */
  471. export const getTiffImageAuth = async (
  472. filename: string
  473. ): Promise<Blob> => {
  474. try {
  475. const response = await axiosInstance.get(
  476. `/auth/f/tif/${filename}`,
  477. {
  478. responseType: 'blob', // 重要:指定响应类型为blob
  479. }
  480. );
  481. return response.data;
  482. } catch (error) {
  483. console.error('Error getting tiff image (auth):', error);
  484. throw error;
  485. }
  486. };
  487. /**
  488. * 图像处理参数v2响应类型
  489. */
  490. export interface GetImageProcessingParamsV2Response {
  491. /** 响应码 */
  492. code: string;
  493. /** 描述信息 */
  494. description: string;
  495. /** 解决方案 */
  496. solution: string;
  497. /** 数据内容 */
  498. data: {
  499. /** 增益 */
  500. contrast: number;
  501. /** 细节 */
  502. detail: number;
  503. /** 动态范围 */
  504. latitude: number;
  505. /** 噪声模式 */
  506. noise: number;
  507. /** 对比度 */
  508. ww_coef: number;
  509. /** 亮度 */
  510. wl_coef: number;
  511. };
  512. }
  513. /**
  514. * 获取当前dcm所用的图像处理参数(v2)
  515. *
  516. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  517. * @returns 当前图像处理参数
  518. * @throws 当获取失败时抛出错误
  519. *
  520. * @example
  521. * ```typescript
  522. * const params = await getImageProcessingParamsV2('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671');
  523. * console.log('当前增益:', params.data.contrast);
  524. * console.log('当前对比度:', params.data.ww_coef);
  525. * ```
  526. */
  527. export const getImageProcessingParamsV2 = async (
  528. sopInstanceUid: string
  529. ): Promise<GetImageProcessingParamsV2Response> => {
  530. try {
  531. const response = await axiosInstance.get<GetImageProcessingParamsV2Response>(
  532. `/auth/image/${sopInstanceUid}/img_proc_params`
  533. );
  534. if (response.data.code !== '0x000000') {
  535. throw new Error(`获取图像处理参数失败: ${response.data.description}`);
  536. }
  537. return response.data;
  538. } catch (error) {
  539. console.error('Error getting image processing params v2:', error);
  540. throw error;
  541. }
  542. };
  543. /**
  544. * 重建dcm请求类型
  545. */
  546. export interface RebuildDcmRequest {
  547. /** 增益 */
  548. contrast: number;
  549. /** 细节 */
  550. detail: number;
  551. /** 动态范围 */
  552. latitude: number;
  553. /** 噪声模式 */
  554. noise: number;
  555. /** 对比度 */
  556. ww_coef: number;
  557. /** 亮度 */
  558. wl_coef: number;
  559. }
  560. /**
  561. * 重建dcm响应类型
  562. */
  563. export interface RebuildDcmResponse {
  564. /** 响应码 */
  565. code: string;
  566. /** 描述信息 */
  567. description: string;
  568. /** 解决方案 */
  569. solution: string;
  570. /** 数据内容 */
  571. data: Record<string, never>;
  572. }
  573. /**
  574. * 重建dcm(保存应用增强参数后的dcm)
  575. *
  576. * @param sopInstanceUid 图像实例 UID (SOP Instance UID)
  577. * @param params 图像处理参数
  578. * @returns 重建结果
  579. * @throws 当重建失败时抛出错误
  580. *
  581. * @example
  582. * ```typescript
  583. * await rebuildDcm('1.2.276.0.1000000.5.1.4.701601461.19649.1749545373.668671', {
  584. * contrast: 5.0,
  585. * detail: 8.0,
  586. * latitude: 6.0,
  587. * noise: 4.0,
  588. * ww_coef: 1.2,
  589. * wl_coef: -0.5
  590. * });
  591. * ```
  592. */
  593. export const rebuildDcm = async (
  594. sopInstanceUid: string,
  595. params: RebuildDcmRequest
  596. ): Promise<RebuildDcmResponse> => {
  597. try {
  598. const response = await axiosInstance.post<RebuildDcmResponse>(
  599. `/auth/image/${sopInstanceUid}/rebuild_dcm`,
  600. params
  601. );
  602. if (response.data.code !== '0x000000') {
  603. throw new Error(`重建dcm失败: ${response.data.description}`);
  604. }
  605. return response.data;
  606. } catch (error) {
  607. console.error('Error rebuilding dcm:', error);
  608. throw error;
  609. }
  610. };