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 * Preloads imageIds metadata in memory
**/ **/
async function prefetchMetadataInformation( export const prefetchMetadataInformation = async (
imageIdsToPrefetch: string[] imageIdsToPrefetch: string[]
): Promise<void> { ): Promise<void> => {
for (let i = 0; i < imageIdsToPrefetch.length; i++) { for (let i = 0; i < imageIdsToPrefetch.length; i++) {
await cornerstoneDICOMImageLoader.wadouri.loadImage(imageIdsToPrefetch[i]) await cornerstoneDICOMImageLoader.wadouri.loadImage(imageIdsToPrefetch[i])
.promise; .promise;
} }
} };
interface FrameInformation { interface FrameInformation {
frameIndex: number; frameIndex: number;
imageIdFrameless: string; imageIdFrameless: string;
} }
function getFrameInformation(imageId: string): FrameInformation { export const getFrameInformation = (imageId: string): FrameInformation => {
if (imageId.includes("wadors:")) { if (imageId.includes("wadors:")) {
const frameIndex = imageId.indexOf("/frames/"); const frameIndex = imageId.indexOf("/frames/");
const imageIdFrameless = const imageIdFrameless =
@ -39,7 +39,7 @@ function getFrameInformation(imageId: string): FrameInformation {
imageIdFrameless, imageIdFrameless,
}; };
} }
} };
/** /**
* Converts a list of imageids possibly referring to multiframe dicom images * 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. * 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 * @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[] = []; const newImageIds: string[] = [];
imageIds.forEach((imageId) => { imageIds.forEach((imageId) => {
const { imageIdFrameless } = getFrameInformation(imageId); const { imageIdFrameless } = getFrameInformation(imageId);
@ -69,6 +69,4 @@ function convertMultiframeImageIds(imageIds: string[]): string[] {
} }
}); });
return newImageIds; return newImageIds;
} };
export { convertMultiframeImageIds, prefetchMetadataInformation };

View File

@ -2,13 +2,17 @@ import { api } from "dicomweb-client";
import dcmjs from "dcmjs"; import dcmjs from "dcmjs";
import { utilities } from "@cornerstonejs/core"; import { utilities } from "@cornerstonejs/core";
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader"; import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader";
import getPixelSpacingInformation from "./getPixelSpacingInformation"; import { getPixelSpacingInformation } from "./getPixelSpacingInformation";
import { convertMultiframeImageIds } from "./convertMultiframeImageIds"; import { convertMultiframeImageIds } from "./convertMultiframeImageIds";
import removeInvalidTags from "./removeInvalidTags"; import { removeInvalidTags } from "./removeInvalidTags";
const { DicomMetaDictionary } = dcmjs.data; const { DicomMetaDictionary } = dcmjs.data;
const { calibratedPixelSpacingMetadataProvider } = utilities; const { calibratedPixelSpacingMetadataProvider } = utilities;
const SOP_INSTANCE_UID = "00080018";
const SERIES_INSTANCE_UID = "0020000E";
const MODALITY = "00080060";
interface CreateImageIdsAndCacheMetaDataOptions { interface CreateImageIdsAndCacheMetaDataOptions {
StudyInstanceUID: string; StudyInstanceUID: string;
SeriesInstanceUID: string; SeriesInstanceUID: string;
@ -17,42 +21,34 @@ interface CreateImageIdsAndCacheMetaDataOptions {
client?: api.DICOMwebClient | null; client?: api.DICOMwebClient | null;
} }
const SOP_INSTANCE_UID = "00080018";
const SERIES_INSTANCE_UID = "0020000E";
// 数据扫描设备 CT、CTA
const MODALITY = "00080060";
/** /**
* DICOMweb ID * DICOMweb ID
* *
* @param options - DICOMweb * @param options - DICOMweb
* @returns ID * @returns ID
*/ */
export default async function createImageIdsAndCacheMetaData( export const createImageIdsAndCacheMetaData = async (
options: CreateImageIdsAndCacheMetaDataOptions options: CreateImageIdsAndCacheMetaDataOptions
): Promise<string[]> { ): Promise<string[]> => {
const studySearchOptions = {
studyInstanceUID: options.StudyInstanceUID,
seriesInstanceUID: options.SeriesInstanceUID,
};
const client = const client =
options.client || options.client ||
new api.DICOMwebClient({ url: options.wadoRsRoot, singlepart: true }); new api.DICOMwebClient({ url: options.wadoRsRoot, singlepart: true });
try { try {
// TODO: 取第一张的metadata看下扫描设备加上限制如果不满足条件就别缓存了 const instances = await client.retrieveSeriesMetadata({
const instances = await client.retrieveSeriesMetadata(studySearchOptions); studyInstanceUID: options.StudyInstanceUID,
seriesInstanceUID: options.SeriesInstanceUID,
});
const modality = (instances[0] as unknown as any)[MODALITY].Value[0]; const modality = (instances[0] as unknown as any)[MODALITY].Value[0];
console.log("modality", modality); console.log("modality", modality);
let imageIds = instances.map((instanceMetaData: any) => { let imageIds = instances.map((instanceMetaData: any) => {
const SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0]; const seriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0];
const SOPInstanceUIDToUse = const sopInstanceUIDToUse =
options.SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0]; options.SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0];
const prefix = "wadors:"; const imageId = `wadors:${options.wadoRsRoot}/studies/${options.StudyInstanceUID}/series/${seriesInstanceUID}/instances/${sopInstanceUIDToUse}/frames/1`;
const imageId = `${prefix}${options.wadoRsRoot}/studies/${options.StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUIDToUse}/frames/1`;
cornerstoneDICOMImageLoader.wadors.metaDataManager.add( cornerstoneDICOMImageLoader.wadors.metaDataManager.add(
imageId, imageId,
@ -61,7 +57,6 @@ export default async function createImageIdsAndCacheMetaData(
return imageId; return imageId;
}); });
// 如果图像 ID 代表多帧信息,则为每个帧创建一个新的图像 ID 列表
imageIds = convertMultiframeImageIds(imageIds); imageIds = convertMultiframeImageIds(imageIds);
imageIds.forEach((imageId) => { imageIds.forEach((imageId) => {
@ -70,23 +65,24 @@ export default async function createImageIdsAndCacheMetaData(
instanceMetaData = removeInvalidTags(instanceMetaData); instanceMetaData = removeInvalidTags(instanceMetaData);
if (instanceMetaData) { if (instanceMetaData) {
// 添加校准的像素间距
const metadata = const metadata =
DicomMetaDictionary.naturalizeDataset(instanceMetaData); DicomMetaDictionary.naturalizeDataset(instanceMetaData);
const pixelSpacing = getPixelSpacingInformation(metadata) as Number[]; const pixelSpacing = getPixelSpacingInformation(metadata) as Number[];
if (pixelSpacing) { if (pixelSpacing) {
const payload = { // FIXME: cornerstone类型定义有问题这里.add方法缺少type属性
rowPixelSpacing: pixelSpacing[0], calibratedPixelSpacingMetadataProvider.add(imageId, {
columnPixelSpacing: pixelSpacing[1],
};
// FIXME: 这个地方cornerstone ts类型约束洗的有问题
// @ts-ignore // @ts-ignore
calibratedPixelSpacingMetadataProvider.add(imageId, payload); rowPixelSpacing: pixelSpacing[0],
// @ts-ignore
columnPixelSpacing: pixelSpacing[1],
});
} }
} }
}); });
return imageIds; return imageIds;
} catch (error) { } catch (error) {
throw new Error("pacs中数据不存在"); throw new Error("PACS 中数据不存在");
} }
} };

View File

@ -26,9 +26,9 @@ interface PixelSpacingResult {
* @param instance - DICOM * @param instance - DICOM
* @returns * @returns
*/ */
export default function getPixelSpacingInformation( export const getPixelSpacingInformation = (
instance: DICOMInstance instance: DICOMInstance
): PixelSpacingResult | number[] | undefined { ): PixelSpacingResult | number[] | undefined => {
const projectionRadiographSOPClassUIDs: string[] = [ 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", // CR Image Storage
"1.2.840.10008.5.1.4.1.1.1.1", // Digital X-Ray Image Storage for Presentation "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( console.warn(
"Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing." "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 { useEffect, useRef } from "react";
import { Enums, RenderingEngine } from "@cornerstonejs/core"; import { Enums, RenderingEngine, cache } from "@cornerstonejs/core";
import initDemo from "./init.demo"; import { initCornerstone } from "./initCornerstoneDicomImageLoader";
import createImageIdsAndCacheMetaData from "./createImageIdsAndCacheMetaData"; import { createImageIdsAndCacheMetaData } from "./createImageIdsAndCacheMetaData";
import { import {
IStackViewport, IStackViewport,
PublicViewportInput, PublicViewportInput,
@ -25,7 +25,7 @@ export const VolumeViewer = (props: VolumeViewerProps) => {
const dicomRef = useRef(null); const dicomRef = useRef(null);
const run = async () => { const run = async () => {
await initDemo(); await initCornerstone();
// Get Cornerstone imageIds and fetch metadata into RAM // Get Cornerstone imageIds and fetch metadata into RAM
const imageIds = await createImageIdsAndCacheMetaData({ const imageIds = await createImageIdsAndCacheMetaData({
StudyInstanceUID, StudyInstanceUID,
@ -48,8 +48,11 @@ export const VolumeViewer = (props: VolumeViewerProps) => {
const stack = imageIds; const stack = imageIds;
await viewport.setStack(stack); await viewport.setStack(stack);
const currentImageIdIndex = viewport.getCurrentImageIdIndex();
viewport.render(); const numImages = viewport.getImageIds().length;
let newImageIdIndex = currentImageIdIndex + 1;
newImageIdIndex = Math.min(newImageIdIndex, numImages - 1);
viewport.setImageIdIndex(newImageIdIndex);
}; };
useEffect(() => { useEffect(() => {
@ -58,7 +61,7 @@ export const VolumeViewer = (props: VolumeViewerProps) => {
return ( return (
<div> <div>
<div ref={dicomRef} style={{ width: 200, height: 200 }}></div> <div ref={dicomRef} style={{ width: 400, height: 400 }}></div>
</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 dicomParser from "dicom-parser";
import * as cornerstone from "@cornerstonejs/core"; import * as cornerstone from "@cornerstonejs/core";
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader"; import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader";
window.cornerstone = cornerstone; const initCornerstoneDICOMImageLoader = () => {
const { preferSizeOverAccuracy, useNorm16Texture } = const { preferSizeOverAccuracy, useNorm16Texture } =
cornerstone.getConfiguration().rendering; cornerstone.getConfiguration().rendering;
export default function initCornerstoneDICOMImageLoader() {
cornerstoneDICOMImageLoader.external.cornerstone = cornerstone; cornerstoneDICOMImageLoader.external.cornerstone = cornerstone;
cornerstoneDICOMImageLoader.external.dicomParser = dicomParser; cornerstoneDICOMImageLoader.external.dicomParser = dicomParser;
cornerstoneDICOMImageLoader.configure({ cornerstoneDICOMImageLoader.configure({
@ -17,14 +17,8 @@ export default function initCornerstoneDICOMImageLoader() {
}, },
}); });
let maxWebWorkers = 1; const config = {
maxWebWorkers: navigator.hardwareConcurrency ?? 7,
if (navigator.hardwareConcurrency) {
maxWebWorkers = Math.min(navigator.hardwareConcurrency, 7);
}
var config = {
maxWebWorkers,
startWebWorkersOnDemand: false, startWebWorkersOnDemand: false,
taskConfiguration: { taskConfiguration: {
decodeTask: { decodeTask: {
@ -35,4 +29,9 @@ export default function initCornerstoneDICOMImageLoader() {
}; };
cornerstoneDICOMImageLoader.webWorkerManager.initialize(config); 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. * @param srcMetadata - The source metadata object containing the tags.
* @returns A new metadata object with invalid tags removed. * @returns A new metadata object with invalid tags removed.
*/ */
function removeInvalidTags( export const removeInvalidTags = (
srcMetadata: Record<string, any> srcMetadata: Record<string, any>
): Record<string, any> { ): Record<string, any> => {
const dstMetadata: Record<string, any> = Object.create(null); const dstMetadata: Record<string, any> = Object.create(null);
const tagIds = Object.keys(srcMetadata); const tagIds = Object.keys(srcMetadata);
let tagValue: any; let tagValue: any;
@ -30,6 +30,4 @@ function removeInvalidTags(
}); });
return dstMetadata; return dstMetadata;
} };
export { removeInvalidTags as default, removeInvalidTags };