feat: 完善了demo ts约束
This commit is contained in:
parent
42d18e116c
commit
c04884ffd3
5
apps/aorta/src/global.d.ts
vendored
Normal file
5
apps/aorta/src/global.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
cornerstone: any;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,74 +9,84 @@ import removeInvalidTags from "./removeInvalidTags";
|
||||||
const { DicomMetaDictionary } = dcmjs.data;
|
const { DicomMetaDictionary } = dcmjs.data;
|
||||||
const { calibratedPixelSpacingMetadataProvider } = utilities;
|
const { calibratedPixelSpacingMetadataProvider } = utilities;
|
||||||
|
|
||||||
export default async function createImageIdsAndCacheMetaData({
|
interface CreateImageIdsAndCacheMetaDataOptions {
|
||||||
StudyInstanceUID,
|
StudyInstanceUID: string;
|
||||||
SeriesInstanceUID,
|
SeriesInstanceUID: string;
|
||||||
SOPInstanceUID = null,
|
SOPInstanceUID?: string | null;
|
||||||
wadoRsRoot,
|
wadoRsRoot: string;
|
||||||
client = null,
|
client?: api.DICOMwebClient | null;
|
||||||
}) {
|
}
|
||||||
const SOP_INSTANCE_UID = "00080018";
|
|
||||||
const SERIES_INSTANCE_UID = "0020000E";
|
|
||||||
const MODALITY = "00080060";
|
|
||||||
|
|
||||||
|
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(
|
||||||
|
options: CreateImageIdsAndCacheMetaDataOptions
|
||||||
|
): Promise<string[]> {
|
||||||
const studySearchOptions = {
|
const studySearchOptions = {
|
||||||
studyInstanceUID: StudyInstanceUID,
|
studyInstanceUID: options.StudyInstanceUID,
|
||||||
seriesInstanceUID: SeriesInstanceUID,
|
seriesInstanceUID: options.SeriesInstanceUID,
|
||||||
};
|
};
|
||||||
|
|
||||||
client = client || new api.DICOMwebClient({ url: wadoRsRoot });
|
const client =
|
||||||
const instances = await client.retrieveSeriesMetadata(studySearchOptions);
|
options.client ||
|
||||||
const modality = instances[0][MODALITY].Value[0];
|
new api.DICOMwebClient({ url: options.wadoRsRoot, singlepart: true });
|
||||||
let imageIds = instances.map((instanceMetaData) => {
|
|
||||||
const SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0];
|
|
||||||
const SOPInstanceUIDToUse =
|
|
||||||
SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0];
|
|
||||||
|
|
||||||
const prefix = "wadors:";
|
try {
|
||||||
|
// TODO: 取第一张的metadata看下扫描设备,加上限制,如果不满足条件,就别缓存了
|
||||||
|
const instances = await client.retrieveSeriesMetadata(studySearchOptions);
|
||||||
|
|
||||||
const imageId =
|
const modality = (instances[0] as unknown as any)[MODALITY].Value[0];
|
||||||
prefix +
|
console.log("modality", modality);
|
||||||
wadoRsRoot +
|
let imageIds = instances.map((instanceMetaData: any) => {
|
||||||
"/studies/" +
|
const SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0];
|
||||||
StudyInstanceUID +
|
const SOPInstanceUIDToUse =
|
||||||
"/series/" +
|
options.SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0];
|
||||||
SeriesInstanceUID +
|
|
||||||
"/instances/" +
|
|
||||||
SOPInstanceUIDToUse +
|
|
||||||
"/frames/1";
|
|
||||||
|
|
||||||
cornerstoneDICOMImageLoader.wadors.metaDataManager.add(
|
const prefix = "wadors:";
|
||||||
imageId,
|
const imageId = `${prefix}${options.wadoRsRoot}/studies/${options.StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUIDToUse}/frames/1`;
|
||||||
instanceMetaData
|
|
||||||
);
|
|
||||||
return imageId;
|
|
||||||
});
|
|
||||||
|
|
||||||
// if the image ids represent multiframe information, creates a new list with one image id per frame
|
cornerstoneDICOMImageLoader.wadors.metaDataManager.add(
|
||||||
// if not multiframe data available, just returns the same list given
|
imageId,
|
||||||
imageIds = convertMultiframeImageIds(imageIds);
|
instanceMetaData
|
||||||
|
);
|
||||||
|
return imageId;
|
||||||
|
});
|
||||||
|
|
||||||
imageIds.forEach((imageId) => {
|
// 如果图像 ID 代表多帧信息,则为每个帧创建一个新的图像 ID 列表
|
||||||
let instanceMetaData =
|
imageIds = convertMultiframeImageIds(imageIds);
|
||||||
cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId);
|
|
||||||
|
|
||||||
// It was using JSON.parse(JSON.stringify(...)) before but it is 8x slower
|
imageIds.forEach((imageId) => {
|
||||||
instanceMetaData = removeInvalidTags(instanceMetaData);
|
let instanceMetaData =
|
||||||
|
cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId);
|
||||||
|
instanceMetaData = removeInvalidTags(instanceMetaData);
|
||||||
|
|
||||||
if (instanceMetaData) {
|
if (instanceMetaData) {
|
||||||
// Add calibrated pixel spacing
|
// 添加校准的像素间距
|
||||||
const metadata = DicomMetaDictionary.naturalizeDataset(instanceMetaData);
|
const metadata =
|
||||||
const pixelSpacing = getPixelSpacingInformation(metadata);
|
DicomMetaDictionary.naturalizeDataset(instanceMetaData);
|
||||||
|
const pixelSpacing = getPixelSpacingInformation(metadata) as Number[];
|
||||||
if (pixelSpacing) {
|
if (pixelSpacing) {
|
||||||
calibratedPixelSpacingMetadataProvider.add(imageId, {
|
const payload = {
|
||||||
rowPixelSpacing: parseFloat(pixelSpacing[0]),
|
rowPixelSpacing: pixelSpacing[0],
|
||||||
columnPixelSpacing: parseFloat(pixelSpacing[1]),
|
columnPixelSpacing: pixelSpacing[1],
|
||||||
});
|
};
|
||||||
|
// FIXME: 这个地方cornerstone ts类型约束洗的有问题
|
||||||
|
// @ts-ignore
|
||||||
|
calibratedPixelSpacingMetadataProvider.add(imageId, payload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
return imageIds;
|
||||||
|
} catch (error) {
|
||||||
return imageIds;
|
throw new Error("pacs中数据不存在");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,35 @@
|
||||||
// See https://github.com/OHIF/Viewers/blob/94a9067fe3d291d30e25a1bda5913511388edea2/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js
|
interface DICOMInstance {
|
||||||
|
PixelSpacing?: number[];
|
||||||
|
ImagerPixelSpacing?: number[];
|
||||||
|
SOPClassUID?: string;
|
||||||
|
PixelSpacingCalibrationType?: string;
|
||||||
|
PixelSpacingCalibrationDescription?: string;
|
||||||
|
EstimatedRadiographicMagnificationFactor?: number;
|
||||||
|
SequenceOfUltrasoundRegions?: {
|
||||||
|
PhysicalDeltaX: number;
|
||||||
|
PhysicalDeltaY: number;
|
||||||
|
}[]; // 根据实际需要调整类型
|
||||||
|
}
|
||||||
|
|
||||||
export default function getPixelSpacingInformation(instance) {
|
interface PixelSpacingResult {
|
||||||
// See http://gdcm.sourceforge.net/wiki/index.php/Imager_Pixel_Spacing
|
PixelSpacing?: number[];
|
||||||
|
type?: string;
|
||||||
|
isProjection?: boolean;
|
||||||
|
PixelSpacingCalibrationType?: string;
|
||||||
|
PixelSpacingCalibrationDescription?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add Ultrasound region spacing
|
/**
|
||||||
// TODO: Add manual calibration
|
* 获取像素间距信息。
|
||||||
|
* 此函数根据 DICOM 实例的不同属性来确定适当的像素间距。
|
||||||
// TODO: Use ENUMS from dcmjs
|
*
|
||||||
const projectionRadiographSOPClassUIDs = [
|
* @param instance - DICOM 实例对象
|
||||||
|
* @returns 像素间距信息
|
||||||
|
*/
|
||||||
|
export default function getPixelSpacingInformation(
|
||||||
|
instance: DICOMInstance
|
||||||
|
): 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", // 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
|
||||||
"1.2.840.10008.5.1.4.1.1.1.1.1", // Digital X-Ray Image Storage – for Processing
|
"1.2.840.10008.5.1.4.1.1.1.1.1", // Digital X-Ray Image Storage – for Processing
|
||||||
|
@ -32,7 +54,9 @@ export default function getPixelSpacingInformation(instance) {
|
||||||
SequenceOfUltrasoundRegions,
|
SequenceOfUltrasoundRegions,
|
||||||
} = instance;
|
} = instance;
|
||||||
|
|
||||||
const isProjection = projectionRadiographSOPClassUIDs.includes(SOPClassUID);
|
const isProjection = SOPClassUID
|
||||||
|
? projectionRadiographSOPClassUIDs.includes(SOPClassUID)
|
||||||
|
: false;
|
||||||
|
|
||||||
const TYPES = {
|
const TYPES = {
|
||||||
NOT_APPLICABLE: "NOT_APPLICABLE",
|
NOT_APPLICABLE: "NOT_APPLICABLE",
|
||||||
|
@ -46,9 +70,6 @@ export default function getPixelSpacingInformation(instance) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProjection && !ImagerPixelSpacing) {
|
if (isProjection && !ImagerPixelSpacing) {
|
||||||
// If only Pixel Spacing is present, and this is a projection radiograph,
|
|
||||||
// PixelSpacing should be used, but the user should be informed that
|
|
||||||
// what it means is unknown
|
|
||||||
return {
|
return {
|
||||||
PixelSpacing,
|
PixelSpacing,
|
||||||
type: TYPES.UNKNOWN,
|
type: TYPES.UNKNOWN,
|
||||||
|
@ -57,10 +78,8 @@ export default function getPixelSpacingInformation(instance) {
|
||||||
} else if (
|
} else if (
|
||||||
PixelSpacing &&
|
PixelSpacing &&
|
||||||
ImagerPixelSpacing &&
|
ImagerPixelSpacing &&
|
||||||
PixelSpacing === ImagerPixelSpacing
|
PixelSpacing.join() === ImagerPixelSpacing.join()
|
||||||
) {
|
) {
|
||||||
// If Imager Pixel Spacing and Pixel Spacing are present and they have the same values,
|
|
||||||
// then the user should be informed that the measurements are at the detector plane
|
|
||||||
return {
|
return {
|
||||||
PixelSpacing,
|
PixelSpacing,
|
||||||
type: TYPES.DETECTOR,
|
type: TYPES.DETECTOR,
|
||||||
|
@ -69,12 +88,8 @@ export default function getPixelSpacingInformation(instance) {
|
||||||
} else if (
|
} else if (
|
||||||
PixelSpacing &&
|
PixelSpacing &&
|
||||||
ImagerPixelSpacing &&
|
ImagerPixelSpacing &&
|
||||||
PixelSpacing !== ImagerPixelSpacing
|
PixelSpacing.join() !== ImagerPixelSpacing.join()
|
||||||
) {
|
) {
|
||||||
// If Imager Pixel Spacing and Pixel Spacing are present and they have different values,
|
|
||||||
// then the user should be informed that these are "calibrated"
|
|
||||||
// (in some unknown manner if Pixel Spacing Calibration Type and/or
|
|
||||||
// Pixel Spacing Calibration Description are absent)
|
|
||||||
return {
|
return {
|
||||||
PixelSpacing,
|
PixelSpacing,
|
||||||
type: TYPES.CALIBRATED,
|
type: TYPES.CALIBRATED,
|
||||||
|
@ -85,9 +100,6 @@ export default function getPixelSpacingInformation(instance) {
|
||||||
} else if (!PixelSpacing && ImagerPixelSpacing) {
|
} else if (!PixelSpacing && ImagerPixelSpacing) {
|
||||||
let CorrectedImagerPixelSpacing = ImagerPixelSpacing;
|
let CorrectedImagerPixelSpacing = ImagerPixelSpacing;
|
||||||
if (EstimatedRadiographicMagnificationFactor) {
|
if (EstimatedRadiographicMagnificationFactor) {
|
||||||
// Note that in IHE Mammo profile compliant displays, the value of Imager Pixel Spacing is required to be corrected by
|
|
||||||
// Estimated Radiographic Magnification Factor and the user informed of that.
|
|
||||||
// TODO: should this correction be done before all of this logic?
|
|
||||||
CorrectedImagerPixelSpacing = ImagerPixelSpacing.map(
|
CorrectedImagerPixelSpacing = ImagerPixelSpacing.map(
|
||||||
(pixelSpacing) =>
|
(pixelSpacing) =>
|
||||||
pixelSpacing / EstimatedRadiographicMagnificationFactor
|
pixelSpacing / EstimatedRadiographicMagnificationFactor
|
||||||
|
@ -104,25 +116,16 @@ export default function getPixelSpacingInformation(instance) {
|
||||||
};
|
};
|
||||||
} else if (
|
} else if (
|
||||||
SequenceOfUltrasoundRegions &&
|
SequenceOfUltrasoundRegions &&
|
||||||
typeof SequenceOfUltrasoundRegions === "object"
|
Array.isArray(SequenceOfUltrasoundRegions) &&
|
||||||
|
SequenceOfUltrasoundRegions.length > 0
|
||||||
) {
|
) {
|
||||||
const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions;
|
const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions[0];
|
||||||
const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10];
|
const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
PixelSpacing: USPixelSpacing,
|
PixelSpacing: USPixelSpacing,
|
||||||
};
|
};
|
||||||
} else if (
|
} else if (isProjection && !ImagerPixelSpacing) {
|
||||||
SequenceOfUltrasoundRegions &&
|
|
||||||
Array.isArray(SequenceOfUltrasoundRegions) &&
|
|
||||||
SequenceOfUltrasoundRegions.length > 1
|
|
||||||
) {
|
|
||||||
console.warn(
|
|
||||||
"Sequence of Ultrasound Regions > one entry. This is not yet implemented, all measurements will be shown in pixels."
|
|
||||||
);
|
|
||||||
} else if (isProjection === false && !ImagerPixelSpacing) {
|
|
||||||
// If only Pixel Spacing is present, and this is not a projection radiograph,
|
|
||||||
// we can stop here
|
|
||||||
return {
|
return {
|
||||||
PixelSpacing,
|
PixelSpacing,
|
||||||
type: TYPES.NOT_APPLICABLE,
|
type: TYPES.NOT_APPLICABLE,
|
||||||
|
@ -134,3 +137,6 @@ export default function getPixelSpacingInformation(instance) {
|
||||||
"Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing."
|
"Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 可选的,如果需要单独导出函数
|
||||||
|
export { getPixelSpacingInformation };
|
||||||
|
|
|
@ -33,8 +33,6 @@ export const VolumeViewer = (props: VolumeViewerProps) => {
|
||||||
wadoRsRoot: "/dicom-web",
|
wadoRsRoot: "/dicom-web",
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(imageIds)
|
|
||||||
|
|
||||||
const renderingEngine = new RenderingEngine(renderingEngineId);
|
const renderingEngine = new RenderingEngine(renderingEngineId);
|
||||||
|
|
||||||
const viewportInput = {
|
const viewportInput = {
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
/**
|
/**
|
||||||
* Remove invalid tags from a metadata and return a new object.
|
* Removes invalid tags from a metadata object and returns a new object.
|
||||||
*
|
*
|
||||||
* At this time it is only removing tags that has `null` or `undefined` values
|
* This function currently removes tags that have `null` or `undefined` values.
|
||||||
* which is our main goal because that breaks when `naturalizeDataset(...)` is
|
* Removing these values is important to prevent errors when processing
|
||||||
* called.
|
* the metadata, such as when calling `naturalizeDataset(...)`.
|
||||||
*
|
*
|
||||||
* Validating the tag id using regex like /^[a-fA-F0-9]{8}$/ make it run
|
* The performance of this function has been considered. Validating each
|
||||||
* +50% slower and looping through all characteres (split+every+Set or simple
|
* tag ID with a regex significantly reduces the function's speed, so this
|
||||||
* FOR+Set) double the time it takes to run. It is currently taking +12ms/1k
|
* approach is avoided. The function is designed to be efficient, with an
|
||||||
* images on average which can change depending on the machine.
|
* average runtime of around +12ms per 1000 images, though this can vary
|
||||||
|
* depending on the machine.
|
||||||
*
|
*
|
||||||
* @param srcMetadata - source metadata
|
* @param srcMetadata - The source metadata object containing the tags.
|
||||||
* @returns new metadata object without invalid tags
|
* @returns A new metadata object with invalid tags removed.
|
||||||
*/
|
*/
|
||||||
function removeInvalidTags(srcMetadata) {
|
function removeInvalidTags(
|
||||||
// Object.create(null) make it ~9% faster
|
srcMetadata: Record<string, any>
|
||||||
const dstMetadata = Object.create(null);
|
): Record<string, any> {
|
||||||
|
const dstMetadata: Record<string, any> = Object.create(null);
|
||||||
const tagIds = Object.keys(srcMetadata);
|
const tagIds = Object.keys(srcMetadata);
|
||||||
let tagValue;
|
let tagValue: any;
|
||||||
|
|
||||||
tagIds.forEach((tagId) => {
|
tagIds.forEach((tagId) => {
|
||||||
tagValue = srcMetadata[tagId];
|
tagValue = srcMetadata[tagId];
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
declare module "@cornerstonejs/dicom-image-loader";
|
declare module "@cornerstonejs/dicom-image-loader";
|
||||||
|
declare module "dcmjs";
|
Loading…
Reference in New Issue
Block a user