feat: 完善了demo ts约束

This commit is contained in:
mozzie 2023-12-20 10:24:54 +08:00
parent 42d18e116c
commit c04884ffd3
6 changed files with 132 additions and 110 deletions

5
apps/aorta/src/global.d.ts vendored Normal file
View File

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

View File

@ -9,42 +9,50 @@ 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 SOP_INSTANCE_UID = "00080018";
const SERIES_INSTANCE_UID = "0020000E"; const SERIES_INSTANCE_UID = "0020000E";
// 数据扫描设备 CT、CTA
const MODALITY = "00080060"; 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 =
options.client ||
new api.DICOMwebClient({ url: options.wadoRsRoot, singlepart: true });
try {
// TODO: 取第一张的metadata看下扫描设备加上限制如果不满足条件就别缓存了
const instances = await client.retrieveSeriesMetadata(studySearchOptions); const instances = await client.retrieveSeriesMetadata(studySearchOptions);
const modality = instances[0][MODALITY].Value[0];
let imageIds = instances.map((instanceMetaData) => { 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 SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0];
const SOPInstanceUIDToUse = const SOPInstanceUIDToUse =
SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0]; options.SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0];
const prefix = "wadors:"; const prefix = "wadors:";
const imageId = `${prefix}${options.wadoRsRoot}/studies/${options.StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUIDToUse}/frames/1`;
const imageId =
prefix +
wadoRsRoot +
"/studies/" +
StudyInstanceUID +
"/series/" +
SeriesInstanceUID +
"/instances/" +
SOPInstanceUIDToUse +
"/frames/1";
cornerstoneDICOMImageLoader.wadors.metaDataManager.add( cornerstoneDICOMImageLoader.wadors.metaDataManager.add(
imageId, imageId,
@ -53,30 +61,32 @@ export default async function createImageIdsAndCacheMetaData({
return imageId; return imageId;
}); });
// if the image ids represent multiframe information, creates a new list with one image id per frame // 如果图像 ID 代表多帧信息,则为每个帧创建一个新的图像 ID 列表
// if not multiframe data available, just returns the same list given
imageIds = convertMultiframeImageIds(imageIds); imageIds = convertMultiframeImageIds(imageIds);
imageIds.forEach((imageId) => { imageIds.forEach((imageId) => {
let instanceMetaData = let instanceMetaData =
cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId); cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId);
// It was using JSON.parse(JSON.stringify(...)) before but it is 8x slower
instanceMetaData = removeInvalidTags(instanceMetaData); 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; return imageIds;
} catch (error) {
throw new Error("pacs中数据不存在");
}
} }

View File

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

View File

@ -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 = {

View File

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

View File

@ -1 +1,2 @@
declare module "@cornerstonejs/dicom-image-loader"; declare module "@cornerstonejs/dicom-image-loader";
declare module "dcmjs";