From 5008849d8218d4d5d9528b48cac40732a2ff1b17 Mon Sep 17 00:00:00 2001 From: mozzie Date: Wed, 20 Dec 2023 12:48:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=87=E6=8D=A2=E5=8D=95=E4=B8=AAvie?= =?UTF-8?q?wport=E7=9A=84=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/aorta/src/global.d.ts | 5 -- .../VolumeViewer/convertMultiframeImageIds.ts | 16 +++--- .../createImageIdsAndCacheMetaData.ts | 54 +++++++++---------- .../getPixelSpacingInformation.ts | 9 ++-- .../Root/Viewer/VolumeViewer/index.tsx | 17 +++--- .../Root/Viewer/VolumeViewer/init.demo.ts | 7 --- .../initCornerstoneDICOMImageLoader.ts | 25 +++++---- .../Viewer/VolumeViewer/removeInvalidTags.ts | 8 ++- 8 files changed, 60 insertions(+), 81 deletions(-) delete mode 100644 apps/aorta/src/modules/Root/Viewer/VolumeViewer/init.demo.ts diff --git a/apps/aorta/src/global.d.ts b/apps/aorta/src/global.d.ts index 3c42f82..e69de29 100644 --- a/apps/aorta/src/global.d.ts +++ b/apps/aorta/src/global.d.ts @@ -1,5 +0,0 @@ -declare global { - interface Window { - cornerstone: any; - } -} diff --git a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/convertMultiframeImageIds.ts b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/convertMultiframeImageIds.ts index 062e3ae..836e809 100644 --- a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/convertMultiframeImageIds.ts +++ b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/convertMultiframeImageIds.ts @@ -4,21 +4,21 @@ import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader"; /** * Preloads imageIds metadata in memory **/ -async function prefetchMetadataInformation( +export const prefetchMetadataInformation = async ( imageIdsToPrefetch: string[] -): Promise { +): Promise => { for (let i = 0; i < imageIdsToPrefetch.length; i++) { await cornerstoneDICOMImageLoader.wadouri.loadImage(imageIdsToPrefetch[i]) .promise; } -} +}; interface FrameInformation { frameIndex: number; imageIdFrameless: string; } -function getFrameInformation(imageId: string): FrameInformation { +export const getFrameInformation = (imageId: string): FrameInformation => { if (imageId.includes("wadors:")) { const frameIndex = imageId.indexOf("/frames/"); const imageIdFrameless = @@ -39,7 +39,7 @@ function getFrameInformation(imageId: string): FrameInformation { imageIdFrameless, }; } -} +}; /** * Converts a list of imageids possibly referring to multiframe dicom images @@ -49,7 +49,7 @@ function getFrameInformation(imageId: string): FrameInformation { * If a particular imageid does not refer to a multiframe image data, it will be just copied into the new list. * @returns new list of imageids where each imageid represents a frame */ -function convertMultiframeImageIds(imageIds: string[]): string[] { +export const convertMultiframeImageIds = (imageIds: string[]): string[] => { const newImageIds: string[] = []; imageIds.forEach((imageId) => { const { imageIdFrameless } = getFrameInformation(imageId); @@ -69,6 +69,4 @@ function convertMultiframeImageIds(imageIds: string[]): string[] { } }); return newImageIds; -} - -export { convertMultiframeImageIds, prefetchMetadataInformation }; +}; diff --git a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/createImageIdsAndCacheMetaData.ts b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/createImageIdsAndCacheMetaData.ts index 57f7abc..144afa0 100644 --- a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/createImageIdsAndCacheMetaData.ts +++ b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/createImageIdsAndCacheMetaData.ts @@ -2,13 +2,17 @@ import { api } from "dicomweb-client"; import dcmjs from "dcmjs"; import { utilities } from "@cornerstonejs/core"; import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader"; -import getPixelSpacingInformation from "./getPixelSpacingInformation"; +import { getPixelSpacingInformation } from "./getPixelSpacingInformation"; import { convertMultiframeImageIds } from "./convertMultiframeImageIds"; -import removeInvalidTags from "./removeInvalidTags"; +import { removeInvalidTags } from "./removeInvalidTags"; const { DicomMetaDictionary } = dcmjs.data; const { calibratedPixelSpacingMetadataProvider } = utilities; +const SOP_INSTANCE_UID = "00080018"; +const SERIES_INSTANCE_UID = "0020000E"; +const MODALITY = "00080060"; + interface CreateImageIdsAndCacheMetaDataOptions { StudyInstanceUID: string; SeriesInstanceUID: string; @@ -17,42 +21,34 @@ interface CreateImageIdsAndCacheMetaDataOptions { client?: api.DICOMwebClient | null; } -const SOP_INSTANCE_UID = "00080018"; -const SERIES_INSTANCE_UID = "0020000E"; -// 数据扫描设备 CT、CTA -const MODALITY = "00080060"; - /** * 从 DICOMweb 服务创建图像 ID 并缓存相关元数据。 * * @param options - 包含图像和 DICOMweb 服务信息的对象 * @returns 图像 ID 数组 */ -export default async function createImageIdsAndCacheMetaData( +export const createImageIdsAndCacheMetaData = async ( options: CreateImageIdsAndCacheMetaDataOptions -): Promise { - const studySearchOptions = { - studyInstanceUID: options.StudyInstanceUID, - seriesInstanceUID: options.SeriesInstanceUID, - }; - +): Promise => { const client = options.client || new api.DICOMwebClient({ url: options.wadoRsRoot, singlepart: true }); try { - // TODO: 取第一张的metadata看下扫描设备,加上限制,如果不满足条件,就别缓存了 - const instances = await client.retrieveSeriesMetadata(studySearchOptions); + const instances = await client.retrieveSeriesMetadata({ + studyInstanceUID: options.StudyInstanceUID, + seriesInstanceUID: options.SeriesInstanceUID, + }); const modality = (instances[0] as unknown as any)[MODALITY].Value[0]; console.log("modality", modality); + let imageIds = instances.map((instanceMetaData: any) => { - const SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0]; - const SOPInstanceUIDToUse = + const seriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0]; + const sopInstanceUIDToUse = options.SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0]; - const prefix = "wadors:"; - const imageId = `${prefix}${options.wadoRsRoot}/studies/${options.StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUIDToUse}/frames/1`; + const imageId = `wadors:${options.wadoRsRoot}/studies/${options.StudyInstanceUID}/series/${seriesInstanceUID}/instances/${sopInstanceUIDToUse}/frames/1`; cornerstoneDICOMImageLoader.wadors.metaDataManager.add( imageId, @@ -61,7 +57,6 @@ export default async function createImageIdsAndCacheMetaData( return imageId; }); - // 如果图像 ID 代表多帧信息,则为每个帧创建一个新的图像 ID 列表 imageIds = convertMultiframeImageIds(imageIds); imageIds.forEach((imageId) => { @@ -70,23 +65,24 @@ export default async function createImageIdsAndCacheMetaData( instanceMetaData = removeInvalidTags(instanceMetaData); if (instanceMetaData) { - // 添加校准的像素间距 const metadata = DicomMetaDictionary.naturalizeDataset(instanceMetaData); const pixelSpacing = getPixelSpacingInformation(metadata) as Number[]; + if (pixelSpacing) { - const payload = { + // FIXME: cornerstone类型定义有问题,这里.add方法缺少type属性 + calibratedPixelSpacingMetadataProvider.add(imageId, { + // @ts-ignore rowPixelSpacing: pixelSpacing[0], + // @ts-ignore columnPixelSpacing: pixelSpacing[1], - }; - // FIXME: 这个地方cornerstone ts类型约束洗的有问题 - // @ts-ignore - calibratedPixelSpacingMetadataProvider.add(imageId, payload); + }); } } }); + return imageIds; } catch (error) { - throw new Error("pacs中数据不存在"); + throw new Error("PACS 中数据不存在"); } -} +}; diff --git a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/getPixelSpacingInformation.ts b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/getPixelSpacingInformation.ts index 081c02e..9b95062 100644 --- a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/getPixelSpacingInformation.ts +++ b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/getPixelSpacingInformation.ts @@ -26,9 +26,9 @@ interface PixelSpacingResult { * @param instance - DICOM 实例对象 * @returns 像素间距信息 */ -export default function getPixelSpacingInformation( +export const getPixelSpacingInformation = ( instance: DICOMInstance -): PixelSpacingResult | number[] | undefined { +): PixelSpacingResult | number[] | undefined => { const projectionRadiographSOPClassUIDs: string[] = [ "1.2.840.10008.5.1.4.1.1.1", // CR Image Storage "1.2.840.10008.5.1.4.1.1.1.1", // Digital X-Ray Image Storage – for Presentation @@ -136,7 +136,4 @@ export default function getPixelSpacingInformation( console.warn( "Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing." ); -} - -// 可选的,如果需要单独导出函数 -export { getPixelSpacingInformation }; +}; diff --git a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/index.tsx b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/index.tsx index 390a35b..2763f87 100644 --- a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/index.tsx +++ b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; -import { Enums, RenderingEngine } from "@cornerstonejs/core"; -import initDemo from "./init.demo"; -import createImageIdsAndCacheMetaData from "./createImageIdsAndCacheMetaData"; +import { Enums, RenderingEngine, cache } from "@cornerstonejs/core"; +import { initCornerstone } from "./initCornerstoneDicomImageLoader"; +import { createImageIdsAndCacheMetaData } from "./createImageIdsAndCacheMetaData"; import { IStackViewport, PublicViewportInput, @@ -25,7 +25,7 @@ export const VolumeViewer = (props: VolumeViewerProps) => { const dicomRef = useRef(null); const run = async () => { - await initDemo(); + await initCornerstone(); // Get Cornerstone imageIds and fetch metadata into RAM const imageIds = await createImageIdsAndCacheMetaData({ StudyInstanceUID, @@ -48,8 +48,11 @@ export const VolumeViewer = (props: VolumeViewerProps) => { const stack = imageIds; await viewport.setStack(stack); - - viewport.render(); + const currentImageIdIndex = viewport.getCurrentImageIdIndex(); + const numImages = viewport.getImageIds().length; + let newImageIdIndex = currentImageIdIndex + 1; + newImageIdIndex = Math.min(newImageIdIndex, numImages - 1); + viewport.setImageIdIndex(newImageIdIndex); }; useEffect(() => { @@ -58,7 +61,7 @@ export const VolumeViewer = (props: VolumeViewerProps) => { return (
-
+
); }; diff --git a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/init.demo.ts b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/init.demo.ts deleted file mode 100644 index fdc0fe3..0000000 --- a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/init.demo.ts +++ /dev/null @@ -1,7 +0,0 @@ -import initCornerstoneDICOMImageLoader from "./initCornerstoneDICOMImageLoader"; -import { init as csRenderInit } from "@cornerstonejs/core"; - -export default async function initDemo() { - initCornerstoneDICOMImageLoader(); - await csRenderInit(); -} diff --git a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/initCornerstoneDICOMImageLoader.ts b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/initCornerstoneDICOMImageLoader.ts index a63854f..5acf5ab 100644 --- a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/initCornerstoneDICOMImageLoader.ts +++ b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/initCornerstoneDICOMImageLoader.ts @@ -1,12 +1,12 @@ +import { init as csRenderInit } from "@cornerstonejs/core"; import dicomParser from "dicom-parser"; import * as cornerstone from "@cornerstonejs/core"; import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader"; -window.cornerstone = cornerstone; -const { preferSizeOverAccuracy, useNorm16Texture } = - cornerstone.getConfiguration().rendering; +const initCornerstoneDICOMImageLoader = () => { + const { preferSizeOverAccuracy, useNorm16Texture } = + cornerstone.getConfiguration().rendering; -export default function initCornerstoneDICOMImageLoader() { cornerstoneDICOMImageLoader.external.cornerstone = cornerstone; cornerstoneDICOMImageLoader.external.dicomParser = dicomParser; cornerstoneDICOMImageLoader.configure({ @@ -17,14 +17,8 @@ export default function initCornerstoneDICOMImageLoader() { }, }); - let maxWebWorkers = 1; - - if (navigator.hardwareConcurrency) { - maxWebWorkers = Math.min(navigator.hardwareConcurrency, 7); - } - - var config = { - maxWebWorkers, + const config = { + maxWebWorkers: navigator.hardwareConcurrency ?? 7, startWebWorkersOnDemand: false, taskConfiguration: { decodeTask: { @@ -35,4 +29,9 @@ export default function initCornerstoneDICOMImageLoader() { }; cornerstoneDICOMImageLoader.webWorkerManager.initialize(config); -} +}; + +export const initCornerstone = async () => { + initCornerstoneDICOMImageLoader(); + await csRenderInit(); +}; diff --git a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/removeInvalidTags.ts b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/removeInvalidTags.ts index 0a96a0f..5e7d8ae 100644 --- a/apps/aorta/src/modules/Root/Viewer/VolumeViewer/removeInvalidTags.ts +++ b/apps/aorta/src/modules/Root/Viewer/VolumeViewer/removeInvalidTags.ts @@ -14,9 +14,9 @@ * @param srcMetadata - The source metadata object containing the tags. * @returns A new metadata object with invalid tags removed. */ -function removeInvalidTags( +export const removeInvalidTags = ( srcMetadata: Record -): Record { +): Record => { const dstMetadata: Record = Object.create(null); const tagIds = Object.keys(srcMetadata); let tagValue: any; @@ -30,6 +30,4 @@ function removeInvalidTags( }); return dstMetadata; -} - -export { removeInvalidTags as default, removeInvalidTags }; +};