feat: 切换单个viewport的图片
This commit is contained in:
parent
c04884ffd3
commit
5008849d82
5
apps/aorta/src/global.d.ts
vendored
5
apps/aorta/src/global.d.ts
vendored
|
@ -1,5 +0,0 @@
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
cornerstone: any;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 };
|
|
||||||
|
|
|
@ -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属性
|
||||||
|
calibratedPixelSpacingMetadataProvider.add(imageId, {
|
||||||
|
// @ts-ignore
|
||||||
rowPixelSpacing: pixelSpacing[0],
|
rowPixelSpacing: pixelSpacing[0],
|
||||||
|
// @ts-ignore
|
||||||
columnPixelSpacing: pixelSpacing[1],
|
columnPixelSpacing: pixelSpacing[1],
|
||||||
};
|
});
|
||||||
// FIXME: 这个地方cornerstone ts类型约束洗的有问题
|
|
||||||
// @ts-ignore
|
|
||||||
calibratedPixelSpacingMetadataProvider.add(imageId, payload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return imageIds;
|
return imageIds;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error("pacs中数据不存在");
|
throw new Error("PACS 中数据不存在");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -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 };
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import initCornerstoneDICOMImageLoader from "./initCornerstoneDICOMImageLoader";
|
|
||||||
import { init as csRenderInit } from "@cornerstonejs/core";
|
|
||||||
|
|
||||||
export default async function initDemo() {
|
|
||||||
initCornerstoneDICOMImageLoader();
|
|
||||||
await csRenderInit();
|
|
||||||
}
|
|
|
@ -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();
|
||||||
|
};
|
||||||
|
|
|
@ -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 };
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user