feat: 切换单个viewport的图片

This commit is contained in:
mozzie 2023-12-20 12:48:57 +08:00
parent c04884ffd3
commit 5008849d82
8 changed files with 60 additions and 81 deletions

View File

@ -1,5 +0,0 @@
declare global {
interface Window {
cornerstone: any;
}
}

View File

@ -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<void> {
): Promise<void> => {
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 };
};

View File

@ -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<string[]> {
const studySearchOptions = {
studyInstanceUID: options.StudyInstanceUID,
seriesInstanceUID: options.SeriesInstanceUID,
};
): Promise<string[]> => {
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 中数据不存在");
}
}
};

View File

@ -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 };
};

View File

@ -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 (
<div>
<div ref={dicomRef} style={{ width: 200, height: 200 }}></div>
<div ref={dicomRef} style={{ width: 400, height: 400 }}></div>
</div>
);
};

View File

@ -1,7 +0,0 @@
import initCornerstoneDICOMImageLoader from "./initCornerstoneDICOMImageLoader";
import { init as csRenderInit } from "@cornerstonejs/core";
export default async function initDemo() {
initCornerstoneDICOMImageLoader();
await csRenderInit();
}

View File

@ -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();
};

View File

@ -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<string, any>
): Record<string, any> {
): Record<string, any> => {
const dstMetadata: Record<string, any> = Object.create(null);
const tagIds = Object.keys(srcMetadata);
let tagValue: any;
@ -30,6 +30,4 @@ function removeInvalidTags(
});
return dstMetadata;
}
export { removeInvalidTags as default, removeInvalidTags };
};