diff --git a/src/components/Dicom/DicomCanvas.vue b/src/components/Dicom/DicomCanvas.vue
index 49f4e1cb..896bdfec 100644
--- a/src/components/Dicom/DicomCanvas.vue
+++ b/src/components/Dicom/DicomCanvas.vue
@@ -78,16 +78,18 @@
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneMath from 'cornerstone-math'
import * as cornerstoneTools from 'cornerstone-tools'
+import metaDataProvider from '@/utils/metaDataProvider'
const scroll = cornerstoneTools.import('util/scrollToIndex')
import Hammer from 'hammerjs'
import getOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/getOrientationString'
import invertOrientationString from '@/views/trials/trials-panel/reading/dicoms/tools/OrientationMarkers/invertOrientationString'
-import calculateSUV from '@/views/trials/trials-panel/reading/dicoms/tools/calculateSUV'
+const calculateSUV = cornerstoneTools.import('util/calculateSUV')
// import requestPoolManager from '@/utils/request-pool'
import ScaleOverlayTool from '@/views/trials/trials-panel/reading/dicoms/tools/ScaleOverlay/ScaleOverlayTool'
import Note_RectangleRoiTool from '@/views/trials/trials-panel/reading/dicoms/tools/RectangleRoi/Note_RectangleRoiTool'
+let isMetaDataProviderAdded = false
cornerstoneTools.external.cornerstone = cornerstone
cornerstoneTools.external.Hammer = Hammer
cornerstoneTools.external.cornerstoneMath = cornerstoneMath
@@ -217,6 +219,11 @@ export default {
// this.stack.instanceId = instanceId
this.toolState.clipPlaying = false
const element = this.$refs.canvas
+ if (!isMetaDataProviderAdded) {
+ // 注册自定义 metaDataProvider:统一 SUV 口径(优先接口,缺失回退 DICOM)
+ cornerstone.metaData.addProvider(metaDataProvider, 100000)
+ isMetaDataProviderAdded = true
+ }
cornerstone.enable(element)
cornerstoneTools.stopClip(this.canvas)
this.toolState.clipPlaying = false
@@ -384,7 +391,7 @@ export default {
this.dicomInfo.age = data.string('x00101010')
this.dicomInfo.sex = data.string('x00100040')
this.dicomInfo.acc = data.string('x00080050') // 登记号
- this.dicomInfo.modality = data.string('x00080060')
+ this.dicomInfo.modality = (data.string('x00080060') || '').trim()
this.dicomInfo.time = this.formatDicomDateTime(
data.string('x00080020'),
data.string('x00080030')
diff --git a/src/components/Dicom/DicomViewer.vue b/src/components/Dicom/DicomViewer.vue
index 9ce5a84e..19f7f78a 100644
--- a/src/components/Dicom/DicomViewer.vue
+++ b/src/components/Dicom/DicomViewer.vue
@@ -363,7 +363,7 @@ import {
getPatientInfo,
editPatientInfo
} from '@/api/trials'
-import { setPTClinicalDataForInstance } from '@/utils/ptClinicalDataCache'
+import { setPTClinicalDataForInstance, clearPTClinicalDataCache } from '@/utils/ptClinicalDataCache'
export default {
name: 'DicomsViewer',
components: {
@@ -476,6 +476,9 @@ export default {
this.type = this.$route.query.type
this.isEdit = parseInt(this.$route.query.showDelete)
},
+ beforeDestroy() {
+ clearPTClinicalDataCache()
+ },
methods: {
anonymousImage() {
@@ -554,7 +557,17 @@ export default {
this.currentDicomCanvas.toolState.clipPlaying = false
this.$nextTick(() => {
this.series = Object.assign({}, dicomSeries)
+ this.seriesList = [this.series]
this.currentDicomCanvas.loadImageStack(this.series)
+ if (
+ this.formData.PatientWeight != null ||
+ this.formData.RadionuclideTotalDose != null ||
+ this.formData.RadionuclideHalfLife != null ||
+ this.formData.RadiopharmaceuticalStartTime != null ||
+ this.formData.AcquisitionTime != null
+ ) {
+ this.cachePtClinicalDataToInstances()
+ }
})
},
loadOtherImageStack(seriesList) {
@@ -568,6 +581,15 @@ export default {
this.$refs[`dicomCanvas${canvasIndex}`].loadImageStack(series)
}
})
+ if (
+ this.formData.PatientWeight != null ||
+ this.formData.RadionuclideTotalDose != null ||
+ this.formData.RadionuclideHalfLife != null ||
+ this.formData.RadiopharmaceuticalStartTime != null ||
+ this.formData.AcquisitionTime != null
+ ) {
+ this.cachePtClinicalDataToInstances()
+ }
})
},
activateDicomCanvas(index) {
diff --git a/src/utils/metaDataProvider.js b/src/utils/metaDataProvider.js
index 85466ac2..c5c59ae8 100644
--- a/src/utils/metaDataProvider.js
+++ b/src/utils/metaDataProvider.js
@@ -1,115 +1,123 @@
-import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
-import store from '@/store'
-import { getPTClinicalDataForInstance } from '@/utils/ptClinicalDataCache'
+import * as cornerstoneWADOImageLoader from "cornerstone-wado-image-loader";
+import store from "@/store";
+import { getPTClinicalDataForInstance } from "@/utils/ptClinicalDataCache";
import {
getImageTypeSubItemFromDataset,
extractOrientationFromDataset,
extractPositionFromDataset,
extractSpacingFromDataset,
extractSliceThicknessFromDataset,
-} from './extractPositioningFromDataset';
+} from "./extractPositioningFromDataset";
function toNumber(val) {
- if (val === undefined || val === null || val === '') return null;
- const n = typeof val === 'number' ? val : parseFloat(val);
+ if (val === undefined || val === null || val === "") return null;
+ const n = typeof val === "number" ? val : parseFloat(val);
return Number.isFinite(n) ? n : null;
}
-function parseDicomTimeToObject(value) {
- if (value === undefined || value === null || value === '') return null;
- const raw = String(value).trim();
- if (!raw) return null;
- const cleaned = raw.replace(/[^\d.]/g, '');
- if (!cleaned) return null;
- const [baseRaw, fracRaw] = cleaned.split('.');
- const base = `${baseRaw || ''}`.padStart(6, '0').slice(-6);
- const hours = toNumber(base.slice(0, 2)) ?? 0;
- const minutes = toNumber(base.slice(2, 4)) ?? 0;
- const seconds = toNumber(base.slice(4, 6)) ?? 0;
- const fractionalSeconds = `${fracRaw || ''}`.padEnd(6, '0').slice(0, 6);
- if (!Number.isFinite(hours) || !Number.isFinite(minutes) || !Number.isFinite(seconds)) return null;
- return { hours, minutes, seconds, fractionalSeconds };
-}
-
-function getFirstSequenceItemDataSet(dataSet, tag) {
- const el = dataSet?.elements?.[tag];
- const item = el?.items?.[0];
- return item?.dataSet;
-}
-
-function getIntString(dataSet, tag) {
- if (!dataSet) return undefined;
- if (typeof dataSet.intString === 'function') {
- return dataSet.intString(tag);
+function normalizeDicomTime(value) {
+ if (value === undefined || value === null || value === "") return null;
+ const num = toNumber(value);
+ if (num != null) {
+ return String(Math.floor(num)).padStart(6, "0");
}
- const raw = dataSet.string(tag);
- const n = raw != null ? parseInt(raw, 10) : NaN;
- return Number.isFinite(n) ? n : undefined;
+ if (typeof value !== "string") return null;
+ const trimmed = value.trim();
+ if (!trimmed) return null;
+ const plain = trimmed.replace(/:/g, "");
+ const parts = plain.split(".");
+ const main = parts[0];
+ const fractional = parts[1];
+ if (!/^\d+$/.test(main)) {
+ return trimmed;
+ }
+ const normalizedMain = main.padStart(6, "0");
+ return fractional ? `${normalizedMain}.${fractional}` : normalizedMain;
}
function getPTClinicalOverrideFromImageId(imageId) {
try {
- const qIndex0 = imageId.indexOf('?');
- if (qIndex0 !== -1) {
- const params0 = new URLSearchParams(imageId.slice(qIndex0 + 1));
- const instanceId0 = params0.get('instanceId');
- if (instanceId0) {
- const cached = getPTClinicalDataForInstance(instanceId0);
- if (cached) return cached;
- }
- }
- const qIndex = imageId.indexOf('?');
+ const qIndex = imageId.indexOf("?");
if (qIndex === -1) return null;
const params = new URLSearchParams(imageId.slice(qIndex + 1));
- const visitTaskId = params.get('visitTaskId');
- const idx = params.get('idx');
- if (!visitTaskId || !idx) return null;
- const parts = idx.split('|');
- const studyIndex = toNumber(parts[0]);
- if (!Number.isInteger(studyIndex) || studyIndex < 0) return null;
+ const visitTaskId = params.get("visitTaskId");
+ const idx = params.get("idx");
- const visitTaskList = store?.state?.reading?.visitTaskList;
- if (!Array.isArray(visitTaskList)) return null;
- const visitTaskInfo = visitTaskList.find(v => v && v.VisitTaskId === visitTaskId);
- const study = visitTaskInfo?.StudyList?.[studyIndex];
- if (!study) return null;
-
- if (
- study.PatientWeight == null &&
- study.RadionuclideTotalDose == null &&
- study.RadionuclideHalfLife == null &&
- study.RadiopharmaceuticalStartTime == null &&
- study.AcquisitionTime == null &&
- study.PatientSex == null
- ) {
- return null;
+ // 场景1:trials/dicoms,imageId 带 visitTaskId + idx,优先使用列表中的最新 study 值
+ if (visitTaskId && idx) {
+ const parts = idx.split("|");
+ const studyIndex = toNumber(parts[0]);
+ if (Number.isInteger(studyIndex) && studyIndex >= 0) {
+ const visitTaskList = store.state.reading.visitTaskList;
+ if (Array.isArray(visitTaskList)) {
+ const visitTaskInfo = visitTaskList.find(
+ (v) => v && String(v.VisitTaskId) === visitTaskId
+ );
+ const study = visitTaskInfo?.StudyList?.[studyIndex];
+ if (
+ study &&
+ !(
+ study.PatientWeight == null &&
+ study.RadionuclideTotalDose == null &&
+ study.RadionuclideHalfLife == null &&
+ study.RadiopharmaceuticalStartTime == null &&
+ study.AcquisitionTime == null &&
+ study.PatientSex == null
+ )
+ ) {
+ return study;
+ }
+ }
+ }
}
- return study;
+
+ // 场景2:src/components/Dicom,imageId 仅带 instanceId,回退到实例缓存
+ const instanceId = params.get("instanceId");
+ if (instanceId) {
+ const cached = getPTClinicalDataForInstance(instanceId);
+ if (cached) return cached;
+ }
+
+ return null;
} catch (e) {
return null;
}
}
function parseImageId(imageId) {
- // build a url by parsing out the url scheme and frame index from the imageId
- const firstColonIndex = imageId.indexOf(':');
-
- let url = imageId.substring(firstColonIndex + 1);
- const frameIndex = url.indexOf('frame=');
-
+ // 兼容 frame 参数不在最后的情况:只移除 frame 参数本身,保留 instanceId/visitTaskId/idx 等其余 query
+ const firstColonIndex = imageId.indexOf(":");
+ const scheme = imageId.substr(0, firstColonIndex);
+ const urlPart = imageId.substring(firstColonIndex + 1);
+ let url = urlPart;
let frame;
+ const qIndex = urlPart.indexOf("?");
+ if (qIndex !== -1) {
+ const base = urlPart.slice(0, qIndex);
+ const query = urlPart.slice(qIndex + 1);
+ const parts = query.split("&").filter(Boolean);
+ const preservedParts = [];
- if (frameIndex !== -1) {
- const frameStr = url.substr(frameIndex + 6);
+ parts.forEach((part) => {
+ const eqIndex = part.indexOf("=");
+ const key = eqIndex === -1 ? part : part.slice(0, eqIndex);
+ const value = eqIndex === -1 ? "" : part.slice(eqIndex + 1);
- frame = parseInt(frameStr, 10);
- url = url.substr(0, frameIndex - 1);
+ if (key === "frame") {
+ const n = value !== "" ? parseInt(value, 10) : NaN;
+ if (Number.isFinite(n)) {
+ frame = n;
+ }
+ return;
+ }
+
+ // 保留原始 query 片段,避免 URLSearchParams 把 | 编码成 %7C 导致 dataSetCache key 对不上
+ preservedParts.push(part);
+ });
+
+ url = preservedParts.length ? `${base}?${preservedParts.join("&")}` : base;
}
- return {
- scheme: imageId.substr(0, firstColonIndex),
- url,
- frame,
- };
+ return { scheme, url, frame };
}
function getNumberValues(dataSet, tag, minimumLength) {
const values = [];
@@ -118,7 +126,7 @@ function getNumberValues(dataSet, tag, minimumLength) {
if (!valueAsString) {
return;
}
- const split = valueAsString.split('\\');
+ const split = valueAsString.split("\\");
if (minimumLength && split.length < minimumLength) {
return;
@@ -157,28 +165,31 @@ function getLutData(lutDataSet, tag, lutDescriptor) {
return lut;
}
function populateSmallestLargestPixelValues(dataSet, imagePixelModule) {
- const pixelRepresentation = dataSet.uint16('x00280103');
+ const pixelRepresentation = dataSet.uint16("x00280103");
if (pixelRepresentation === 0) {
- imagePixelModule.smallestPixelValue = dataSet.uint16('x00280106');
- imagePixelModule.largestPixelValue = dataSet.uint16('x00280107');
+ imagePixelModule.smallestPixelValue = dataSet.uint16("x00280106");
+ imagePixelModule.largestPixelValue = dataSet.uint16("x00280107");
} else {
- imagePixelModule.smallestPixelValue = dataSet.int16('x00280106');
- imagePixelModule.largestPixelValue = dataSet.int16('x00280107');
+ imagePixelModule.smallestPixelValue = dataSet.int16("x00280106");
+ imagePixelModule.largestPixelValue = dataSet.int16("x00280107");
}
- imagePixelModule.largestPixelValue = imagePixelModule.largestPixelValue === 0 ? undefined : imagePixelModule.largestPixelValue;
+ imagePixelModule.largestPixelValue =
+ imagePixelModule.largestPixelValue === 0
+ ? undefined
+ : imagePixelModule.largestPixelValue;
}
function populatePaletteColorLut(dataSet, imagePixelModule) {
imagePixelModule.redPaletteColorLookupTableDescriptor = getLutDescriptor(
dataSet,
- 'x00281101'
+ "x00281101"
);
imagePixelModule.greenPaletteColorLookupTableDescriptor = getLutDescriptor(
dataSet,
- 'x00281102'
+ "x00281102"
);
imagePixelModule.bluePaletteColorLookupTableDescriptor = getLutDescriptor(
dataSet,
- 'x00281103'
+ "x00281103"
);
// The first Palette Color Lookup Table Descriptor value is the number of entries in the lookup table.
@@ -217,115 +228,165 @@ function populatePaletteColorLut(dataSet, imagePixelModule) {
imagePixelModule.redPaletteColorLookupTableData = getLutData(
dataSet,
- 'x00281201',
+ "x00281201",
imagePixelModule.redPaletteColorLookupTableDescriptor
);
imagePixelModule.greenPaletteColorLookupTableData = getLutData(
dataSet,
- 'x00281202',
+ "x00281202",
imagePixelModule.greenPaletteColorLookupTableDescriptor
);
imagePixelModule.bluePaletteColorLookupTableData = getLutData(
dataSet,
- 'x00281203',
+ "x00281203",
imagePixelModule.bluePaletteColorLookupTableDescriptor
);
}
function getSpacingBetweenSlices(dataSet) {
if (dataSet?.elements?.x00180088) {
- return dataSet.floatString('x00180088');
+ return dataSet.floatString("x00180088");
}
const pixelMeasuresSequence = dataSet?.elements?.x00289110;
if (
pixelMeasuresSequence?.items?.length &&
pixelMeasuresSequence.items[0]?.dataSet?.elements?.x00180088
) {
- return pixelMeasuresSequence.items[0].dataSet.floatString('x00180088');
+ return pixelMeasuresSequence.items[0].dataSet.floatString("x00180088");
}
}
function metaDataProvider(type, imageId) {
const parsedImageId = parseImageId(imageId);
- const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url);
+ const dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(
+ parsedImageId.url
+ );
if (!dataSet) {
return;
}
const ptOverride = getPTClinicalOverrideFromImageId(imageId);
- if (type === 'generalSeriesModule') {
- // 参照 cornerstoneWADOImageLoader 的 module 结构返回,避免调用方拿不到预期字段
- const dicomParser = cornerstoneWADOImageLoader?.external?.dicomParser;
- const modality = dataSet.string('x00080060');
- const seriesDateRaw = dataSet.string('x00080021') || '';
- const seriesTimeRaw = dataSet.string('x00080031') || '';
- const acquisitionDateRaw = dataSet.string('x00080022') || '';
- const acquisitionTimeRaw = dataSet.string('x00080032') || '';
- const seriesTimeValue = ptOverride?.AcquisitionTime ?? seriesTimeRaw;
+ if (type === "generalSeriesModule") {
+ const { dicomParser } = cornerstoneWADOImageLoader.external || {};
+ const overrideTime = normalizeDicomTime(ptOverride?.AcquisitionTime);
+ const seriesTimeRaw =
+ overrideTime ||
+ dataSet.string("x00080031") ||
+ dataSet.string("x00080032") ||
+ "";
+ const acquisitionTimeRaw =
+ overrideTime ||
+ dataSet.string("x00080032") ||
+ dataSet.string("x00080031") ||
+ "";
return {
- modality,
- seriesInstanceUID: dataSet.string('x0020000e'),
- seriesNumber: getIntString(dataSet, 'x00200011'),
- studyInstanceUID: dataSet.string('x0020000d'),
- seriesDate: dicomParser?.parseDA ? dicomParser.parseDA(seriesDateRaw) : seriesDateRaw,
- seriesTime: dicomParser?.parseTM ? dicomParser.parseTM(seriesTimeValue || '') : parseDicomTimeToObject(seriesTimeValue),
- acquisitionDate: dicomParser?.parseDA ? dicomParser.parseDA(acquisitionDateRaw) : acquisitionDateRaw,
- acquisitionTime: dicomParser?.parseTM ? dicomParser.parseTM(acquisitionTimeRaw || '') : parseDicomTimeToObject(acquisitionTimeRaw)
+ modality: dataSet.string("x00080060"),
+ seriesInstanceUID: dataSet.string("x0020000e"),
+ seriesNumber: dataSet.intString("x00200011"),
+ studyInstanceUID: dataSet.string("x0020000d"),
+ seriesDate: dicomParser.parseDA(dataSet.string("x00080021")),
+ // 2D SUV 计算读取的是 seriesTime,这里同步使用用户录入采集时间
+ // seriesTime: dicomParser.parseTM(String(seriesTimeRaw)),
+ seriesTime: dicomParser.parseTM(dataSet.string('x00080031') || ''),
+ acquisitionDate: dicomParser.parseDA(dataSet.string("x00080022") || ""),
+ acquisitionTime: dicomParser.parseTM(String(acquisitionTimeRaw)),
};
}
- if (type === 'patientStudyModule') {
- // 参照 cornerstoneWADOImageLoader 的 module 结构返回
- const patientWeightRaw = dataSet.floatString('x00101030') || dataSet.string('x00101030');
- // const patientSexRaw = dataSet.string('x00100040');
- const patientSizeRaw = dataSet.floatString('x00101020') || dataSet.string('x00101020');
- const patientAge = getIntString(dataSet, 'x00101010');
- const patientWeight = toNumber(ptOverride?.PatientWeight ?? patientWeightRaw);
- // const patientSex = ptOverride?.PatientSex ?? patientSexRaw;
- const patientSize = toNumber(patientSizeRaw);
+ if (type === "patientStudyModule") {
+ const weightOverride = ptOverride?.PatientWeight;
+ const weight =
+ weightOverride
+ ? toNumber(weightOverride)
+ : dataSet.floatString("x00101030");
return {
- patientAge,
- patientWeight,
- // patientSex,
- patientSize
+ patientAge: dataSet.intString("x00101010"),
+ patientSize: dataSet.floatString("x00101020"),
+ patientWeight: weight,
};
}
- if (type === 'petIsotopeModule') {
- // 统一 SUV 口径:优先使用接口/人工录入的 PT 临床数据,缺失时回退读取 DICOM Tag
- // 同时保持与 cornerstoneWADOImageLoader 返回结构一致(radiopharmaceuticalInfo.*)
- const dicomParser = cornerstoneWADOImageLoader?.external?.dicomParser;
- const radioPharmItem = getFirstSequenceItemDataSet(dataSet, 'x00540016');
- const startTimeRaw = radioPharmItem?.string?.('x00181072') || dataSet.string('x00181072');
- const totalDoseRaw = radioPharmItem?.floatString?.('x00181074') || radioPharmItem?.string?.('x00181074') || dataSet.floatString('x00181074') || dataSet.string('x00181074');
- const halfLifeRaw = radioPharmItem?.floatString?.('x00181075') || radioPharmItem?.string?.('x00181075') || dataSet.floatString('x00181075') || dataSet.string('x00181075');
- const startTimeValue = ptOverride?.RadiopharmaceuticalStartTime ?? startTimeRaw;
- const radiopharmaceuticalStartTime = dicomParser?.parseTM
- ? dicomParser.parseTM(startTimeValue || '')
- : parseDicomTimeToObject(startTimeValue);
- const radionuclideTotalDose = toNumber(ptOverride?.RadionuclideTotalDose ?? totalDoseRaw);
- const radionuclideHalfLife = toNumber(ptOverride?.RadionuclideHalfLife ?? halfLifeRaw);
+
+ if (type === "petIsotopeModule") {
+ const { dicomParser } = cornerstoneWADOImageLoader.external || {};
+ const radiopharmaceuticalInfo = dataSet.elements.x00540016;
+
+ if (radiopharmaceuticalInfo === undefined) {
+ return;
+ }
+ const firstRadiopharmaceuticalInfoDataSet =
+ radiopharmaceuticalInfo.items[0].dataSet;
+
+ const startTimeRaw =
+ firstRadiopharmaceuticalInfoDataSet.string("x00181072") || "";
+ const totalDoseRaw =
+ firstRadiopharmaceuticalInfoDataSet.floatString("x00181074");
+ const halfLifeRaw =
+ firstRadiopharmaceuticalInfoDataSet.floatString("x00181075");
+ let startTimeValue = normalizeDicomTime(
+ ptOverride?.RadiopharmaceuticalStartTime
+ );
+ if (!startTimeValue && startTimeRaw) {
+ startTimeValue = normalizeDicomTime(startTimeRaw) || startTimeRaw;
+ }
+ const radiopharmaceuticalStartTime = dicomParser.parseTM(
+ String(startTimeValue)
+ );
+ const overrideTotalDose =
+ ptOverride?.RadionuclideTotalDose != null &&
+ ptOverride.RadionuclideTotalDose !== ""
+ ? toNumber(ptOverride.RadionuclideTotalDose)
+ : null;
+ const overrideHalfLife =
+ ptOverride?.RadionuclideHalfLife != null &&
+ ptOverride.RadionuclideHalfLife !== ""
+ ? toNumber(ptOverride.RadionuclideHalfLife)
+ : null;
+ const radionuclideTotalDose =
+ overrideTotalDose != null ? overrideTotalDose : toNumber(totalDoseRaw);
+ const radionuclideHalfLife =
+ overrideHalfLife != null ? overrideHalfLife : toNumber(halfLifeRaw);
return {
radiopharmaceuticalInfo: {
radiopharmaceuticalStartTime,
radionuclideTotalDose,
- radionuclideHalfLife
- }
+ radionuclideHalfLife,
+ },
};
}
- if (type === 'imagePlaneModule') {
+ if (type === "imagePlaneModule") {
// const imageOrientationPatient = getNumberValues(dataSet, 'x00200037', 6);
// const imagePositionPatient = getNumberValues(dataSet, 'x00200032', 3);
// const pixelSpacing = getNumberValues(dataSet, 'x00280030', 2);
- const imagePixelSpacing = getNumberValues(dataSet, 'x00181164', 2);
- const frameIndex = parsedImageId.frame !== undefined ? parsedImageId.frame - 1 : undefined;
- const imageOrientationPatient = extractOrientationFromDataset(dataSet, frameIndex);
+ const imagePixelSpacing = getNumberValues(dataSet, "x00181164", 2);
+ const frameIndex =
+ parsedImageId.frame !== undefined ? parsedImageId.frame - 1 : undefined;
+ const imageOrientationPatient = extractOrientationFromDataset(
+ dataSet,
+ frameIndex
+ );
let imagePositionPatient = extractPositionFromDataset(dataSet, frameIndex);
const pixelSpacing = extractSpacingFromDataset(dataSet, frameIndex);
- const sliceThickness = extractSliceThicknessFromDataset(dataSet, frameIndex);
+ const sliceThickness = extractSliceThicknessFromDataset(
+ dataSet,
+ frameIndex
+ );
const modality = dataSet.string("x00080060");
- if (modality && modality.includes('NM') && parsedImageId.frame !== undefined && parsedImageId.frame > 1) {
+ if (
+ modality &&
+ modality.includes("NM") &&
+ parsedImageId.frame !== undefined &&
+ parsedImageId.frame > 1
+ ) {
const spacingBetweenSlices = getSpacingBetweenSlices(dataSet);
- const step = spacingBetweenSlices !== undefined ? spacingBetweenSlices : sliceThickness;
- if (imageOrientationPatient && imagePositionPatient && step !== undefined && frameIndex !== undefined) {
+ const step =
+ spacingBetweenSlices !== undefined
+ ? spacingBetweenSlices
+ : sliceThickness;
+ if (
+ imageOrientationPatient &&
+ imagePositionPatient &&
+ step !== undefined &&
+ frameIndex !== undefined
+ ) {
const rowCosines = [
parseFloat(imageOrientationPatient[0]),
parseFloat(imageOrientationPatient[1]),
@@ -352,7 +413,11 @@ function metaDataProvider(type, imageId) {
}
}
- const estimatedRadiographicMagnificationFactor = getNumberValues(dataSet, 'x00181114', 2);
+ const estimatedRadiographicMagnificationFactor = getNumberValues(
+ dataSet,
+ "x00181114",
+ 2
+ );
let columnPixelSpacing = null;
let rowPixelSpacing = null;
@@ -361,8 +426,10 @@ function metaDataProvider(type, imageId) {
rowPixelSpacing = pixelSpacing[0];
columnPixelSpacing = pixelSpacing[1];
} else if (imagePixelSpacing && estimatedRadiographicMagnificationFactor) {
- rowPixelSpacing = imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
- columnPixelSpacing = imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
+ rowPixelSpacing =
+ imagePixelSpacing[0] / estimatedRadiographicMagnificationFactor[0];
+ columnPixelSpacing =
+ imagePixelSpacing[1] / estimatedRadiographicMagnificationFactor[1];
} else if (imagePixelSpacing && !estimatedRadiographicMagnificationFactor) {
rowPixelSpacing = imagePixelSpacing[0];
columnPixelSpacing = imagePixelSpacing[1];
@@ -386,37 +453,37 @@ function metaDataProvider(type, imageId) {
}
return {
- frameOfReferenceUID: dataSet.string('x00200052'),
- rows: dataSet.uint16('x00280010'),
- columns: dataSet.uint16('x00280011'),
+ frameOfReferenceUID: dataSet.string("x00200052"),
+ rows: dataSet.uint16("x00280010"),
+ columns: dataSet.uint16("x00280011"),
imageOrientationPatient,
rowCosines,
columnCosines,
imagePositionPatient,
sliceThickness,
- sliceLocation: dataSet.floatString('x00201041'),
+ sliceLocation: dataSet.floatString("x00201041"),
pixelSpacing,
rowPixelSpacing,
columnPixelSpacing,
};
}
- if (type === 'imagePixelModule') {
+ if (type === "imagePixelModule") {
const imagePixelModule = {
- samplesPerPixel: dataSet.uint16('x00280002'),
- photometricInterpretation: dataSet.string('x00280004'),
- rows: dataSet.uint16('x00280010'),
- columns: dataSet.uint16('x00280011'),
- bitsAllocated: dataSet.uint16('x00280100'),
- bitsStored: dataSet.uint16('x00280101'),
- highBit: dataSet.uint16('x00280102'),
- pixelRepresentation: dataSet.uint16('x00280103'),
- planarConfiguration: dataSet.uint16('x00280006'),
- pixelAspectRatio: dataSet.string('x00280034'),
+ samplesPerPixel: dataSet.uint16("x00280002"),
+ photometricInterpretation: dataSet.string("x00280004"),
+ rows: dataSet.uint16("x00280010"),
+ columns: dataSet.uint16("x00280011"),
+ bitsAllocated: dataSet.uint16("x00280100"),
+ bitsStored: dataSet.uint16("x00280101"),
+ highBit: dataSet.uint16("x00280102"),
+ pixelRepresentation: dataSet.uint16("x00280103"),
+ planarConfiguration: dataSet.uint16("x00280006"),
+ pixelAspectRatio: dataSet.string("x00280034"),
};
populateSmallestLargestPixelValues(dataSet, imagePixelModule);
if (
- imagePixelModule.photometricInterpretation === 'PALETTE COLOR' &&
+ imagePixelModule.photometricInterpretation === "PALETTE COLOR" &&
dataSet.elements.x00281101
) {
populatePaletteColorLut(dataSet, imagePixelModule);
diff --git a/src/utils/ptClinicalDataCache.js b/src/utils/ptClinicalDataCache.js
index 887fef3c..55d76550 100644
--- a/src/utils/ptClinicalDataCache.js
+++ b/src/utils/ptClinicalDataCache.js
@@ -6,12 +6,12 @@ function normalizeId(id) {
const s = String(id).trim()
return s ? s : null
}
-
+
export function setPTClinicalDataForInstance(instanceId, clinicalData) {
const key = normalizeId(instanceId)
if (!key) return
if (!clinicalData || typeof clinicalData !== 'object') return
- instanceIdToClinicalData.set(key, clinicalData)
+ instanceIdToClinicalData.set(key, { ...clinicalData })
}
export function getPTClinicalDataForInstance(instanceId) {
@@ -20,7 +20,12 @@ export function getPTClinicalDataForInstance(instanceId) {
return instanceIdToClinicalData.get(key) || null
}
+export function deletePTClinicalDataForInstance(instanceId) {
+ const key = normalizeId(instanceId)
+ if (!key) return
+ instanceIdToClinicalData.delete(key)
+}
+
export function clearPTClinicalDataCache() {
instanceIdToClinicalData.clear()
}
-
diff --git a/src/views/trials/trials-panel/reading/ad-review/index.vue b/src/views/trials/trials-panel/reading/ad-review/index.vue
index 085140c4..0a85c255 100644
--- a/src/views/trials/trials-panel/reading/ad-review/index.vue
+++ b/src/views/trials/trials-panel/reading/ad-review/index.vue
@@ -313,6 +313,10 @@ export default {
isExistsClinicalData: {
type: Boolean,
required: true
+ },
+ imageToolType: {
+ type: Number,
+ required: true
}
},
data() {
@@ -549,9 +553,9 @@ export default {
this.signVisible = false
// window.location.reload()
// window.opener.postMessage('refreshTaskList', window.location)
- // 设置当前任务阅片状态为已读
+ // 设置当前任务阅片状态为已读
this.adInfo.ReadingTaskState = 2
- const res = await getAutoCutNextTask()
+ const res = await getAutoCutNextTask({imageToolType: this.imageToolType})
var isAutoTask = res.Result.AutoCutNextTask
if (isAutoTask) {
// store.dispatch('reading/resetVisitTasks')
diff --git a/src/views/trials/trials-panel/reading/dicoms/components/DicomCanvas.vue b/src/views/trials/trials-panel/reading/dicoms/components/DicomCanvas.vue
index 3cba61c9..e4ac3899 100644
--- a/src/views/trials/trials-panel/reading/dicoms/components/DicomCanvas.vue
+++ b/src/views/trials/trials-panel/reading/dicoms/components/DicomCanvas.vue
@@ -1134,7 +1134,8 @@ export default {
// resolve()
// })
this.loading = true
- cornerstone.metaData.addProvider(metaDataProvider, 1);
+ cornerstone.metaData.removeProvider(metaDataProvider)
+ cornerstone.metaData.addProvider(metaDataProvider, 100000)
cornerstone.loadAndCacheImage(this.stack.imageIds[this.stack.currentImageIdIndex])
.then(async image => {
if (this.stack.imageIds.indexOf(image.imageId) !== -1) {
diff --git a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/getPTImageIdInstanceMetadata.js b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/getPTImageIdInstanceMetadata.js
index 1decd99c..f0e1c75b 100644
--- a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/getPTImageIdInstanceMetadata.js
+++ b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/getPTImageIdInstanceMetadata.js
@@ -2,20 +2,37 @@ import { metaData } from '@cornerstonejs/core'
// import { InstanceMetadata } from '@cornerstonejs/calculate-suv'
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
import { getPTClinicalDataForInstance } from '@/utils/ptClinicalDataCache'
+
function parseImageId(imageId) {
- // build a url by parsing out the url scheme and frame index from the imageId
const firstColonIndex = imageId.indexOf(':')
-
- let url = imageId.substring(firstColonIndex + 1)
- const frameIndex = url.indexOf('frame=')
-
+ const urlPart = imageId.substring(firstColonIndex + 1)
+ let url = urlPart
let frame
+ const qIndex = urlPart.indexOf('?')
- if (frameIndex !== -1) {
- const frameStr = url.substr(frameIndex + 6)
+ if (qIndex !== -1) {
+ const base = urlPart.slice(0, qIndex)
+ const query = urlPart.slice(qIndex + 1)
+ const parts = query.split('&').filter(Boolean)
+ const preservedParts = []
- frame = parseInt(frameStr, 10)
- url = url.substr(0, frameIndex - 1)
+ parts.forEach((part) => {
+ const eqIndex = part.indexOf('=')
+ const key = eqIndex === -1 ? part : part.slice(0, eqIndex)
+ const value = eqIndex === -1 ? '' : part.slice(eqIndex + 1)
+
+ if (key === 'frame') {
+ const n = value !== '' ? parseInt(value, 10) : NaN
+ if (Number.isFinite(n)) {
+ frame = n
+ }
+ return
+ }
+
+ preservedParts.push(part)
+ })
+
+ url = preservedParts.length ? `${base}?${preservedParts.join('&')}` : base
}
return {
@@ -24,6 +41,7 @@ function parseImageId(imageId) {
frame
}
}
+
function getMetaData(type, imageId) {
// const { dicomParser } = cornerstoneDICOMImageLoader.external
const parsedImageId = parseImageId(imageId)
@@ -33,6 +51,7 @@ function getMetaData(type, imageId) {
if (!dataSet) {
return
}
+
if (type === 'petImageModule') {
// 1340137.4196974 240000
// console.log(dataSet.string('x00541300'), dataSet.string('x00181242'))
@@ -42,194 +61,69 @@ function getMetaData(type, imageId) {
}
}
}
+
export default function getPTImageIdInstanceMetadata(imageId) {
const instanceId = getInstanceIdFromImageId(imageId)
- // 统一 SUV 口径:若接口存在 PT 临床数据,则优先覆盖 DICOM 元数据
const ptClinicalData = instanceId ? getPTClinicalDataForInstance(instanceId) : null
const petSequenceModule = metaData.get('petIsotopeModule', imageId)
+ const generalSeriesModule = metaData.get('generalSeriesModule', imageId) || {}
+ const patientStudyModule = metaData.get('patientStudyModule', imageId) || {}
+ const ptSeriesModule = metaData.get('petSeriesModule', imageId) || {}
+ const ptImageModule = getMetaData('petImageModule', imageId) || {}
+ const radiopharmaceuticalInfo = petSequenceModule?.radiopharmaceuticalInfo
- const generalSeriesModule = metaData.get('generalSeriesModule', imageId)
- const patientStudyModule = metaData.get('patientStudyModule', imageId)
-
- const ptSeriesModule = metaData.get('petSeriesModule', imageId)
- // const ptImageModule = metaData.get('petImageModule', imageId)
-
- const ptImageModule = getMetaData('petImageModule', imageId)
- if (!petSequenceModule) {
+ if (!radiopharmaceuticalInfo) {
throw new Error('petSequenceModule metadata is required')
}
- const radiopharmaceuticalInfo = petSequenceModule.radiopharmaceuticalInfo
-
- const { seriesDate, seriesTime, acquisitionDate, acquisitionTime } =
- generalSeriesModule
- var { patientWeight } = patientStudyModule
- if (ptClinicalData && ptClinicalData.PatientWeight != null && ptClinicalData.PatientWeight !== '') {
- patientWeight = parseFloat(ptClinicalData.PatientWeight)
- }
- // console.log('更改前:', patientWeight)
- // patientWeight = patientWeight * 10
- // console.log('更改后:', patientWeight)
- const { correctedImage, units, decayCorrection } = ptSeriesModule
- const totalDose = ptClinicalData && ptClinicalData.RadionuclideTotalDose != null && ptClinicalData.RadionuclideTotalDose !== ''
- ? parseFloat(ptClinicalData.RadionuclideTotalDose)
- : radiopharmaceuticalInfo.radionuclideTotalDose
- const halfLife = ptClinicalData && ptClinicalData.RadionuclideHalfLife != null && ptClinicalData.RadionuclideHalfLife !== ''
- ? parseFloat(ptClinicalData.RadionuclideHalfLife)
- : radiopharmaceuticalInfo.radionuclideHalfLife
- const startTimeOverride = ptClinicalData ? normalizeDicomTime(ptClinicalData.RadiopharmaceuticalStartTime) : null
- const acquisitionTimeOverride = ptClinicalData ? normalizeDicomTime(ptClinicalData.AcquisitionTime) : null
-
- if (
- seriesDate === undefined ||
- seriesTime === undefined ||
- patientWeight === undefined ||
- acquisitionDate === undefined ||
- (acquisitionTimeOverride || acquisitionTime) === undefined ||
- correctedImage === undefined ||
- units === undefined ||
- decayCorrection === undefined ||
- totalDose === undefined ||
- halfLife === undefined ||
- (radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime === undefined &&
- seriesDate === undefined &&
- (startTimeOverride || radiopharmaceuticalInfo.radiopharmaceuticalStartTime) === undefined)
- //
- ) {
- throw new Error('required metadata are missing')
- }
+ const patientWeight = toNumber(
+ ptClinicalData?.PatientWeight,
+ patientStudyModule.patientWeight
+ )
+ const totalDose = toNumber(
+ ptClinicalData?.RadionuclideTotalDose,
+ radiopharmaceuticalInfo.radionuclideTotalDose
+ )
+ const halfLife = toNumber(
+ ptClinicalData?.RadionuclideHalfLife,
+ radiopharmaceuticalInfo.radionuclideHalfLife
+ )
+ const startTime = firstValue(
+ normalizeDicomTime(ptClinicalData?.RadiopharmaceuticalStartTime),
+ toDicomTimeString(radiopharmaceuticalInfo.radiopharmaceuticalStartTime)
+ )
+ const acquisitionTime = firstValue(
+ normalizeDicomTime(ptClinicalData?.AcquisitionTime),
+ toDicomTimeString(generalSeriesModule.acquisitionTime)
+ )
const instanceMetadata = {
- CorrectedImage: correctedImage,
- Units: units,
+ CorrectedImage: ptSeriesModule.correctedImage,
+ Units: ptSeriesModule.units,
RadionuclideHalfLife: halfLife,
RadionuclideTotalDose: totalDose,
- DecayCorrection: decayCorrection,
+ DecayCorrection: ptSeriesModule.decayCorrection,
PatientWeight: patientWeight,
- SeriesDate: seriesDate,
- SeriesTime: seriesTime,
- AcquisitionDate: acquisitionDate,
- AcquisitionTime: acquisitionTimeOverride || acquisitionTime
+ SeriesDate: toDicomDateString(generalSeriesModule.seriesDate),
+ SeriesTime: toDicomTimeString(generalSeriesModule.seriesTime),
+ AcquisitionDate: toDicomDateString(generalSeriesModule.acquisitionDate),
+ AcquisitionTime: acquisitionTime
}
- if (
- radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime &&
- radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== undefined &&
- typeof radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime === 'string'
- ) {
- instanceMetadata.RadiopharmaceuticalStartDateTime =
- radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime
- }
-
- if (
- radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime &&
- radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== undefined &&
- typeof radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== 'string'
- ) {
- const dateString = convertInterfaceDateToString(
- radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime
- )
- instanceMetadata.RadiopharmaceuticalStartDateTime = dateString
- }
-
- if (
- instanceMetadata.AcquisitionDate &&
- instanceMetadata.AcquisitionDate !== undefined &&
- typeof instanceMetadata.AcquisitionDate !== 'string'
- ) {
- const dateString = convertInterfaceDateToString(
- instanceMetadata.AcquisitionDate
- )
- instanceMetadata.AcquisitionDate = dateString
- }
-
- if (
- instanceMetadata.SeriesDate &&
- instanceMetadata.SeriesDate !== undefined &&
- typeof instanceMetadata.SeriesDate !== 'string'
- ) {
- const dateString = convertInterfaceDateToString(
- instanceMetadata.SeriesDate
- )
- instanceMetadata.SeriesDate = dateString
- }
-
- if (
- radiopharmaceuticalInfo.radiopharmaceuticalStartTime &&
- radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== undefined &&
- typeof radiopharmaceuticalInfo.radiopharmaceuticalStartTime === 'string'
- ) {
- instanceMetadata.RadiopharmaceuticalStartTime =
- radiopharmaceuticalInfo.radiopharmaceuticalStartTime
- }
-
- if (
- radiopharmaceuticalInfo.radiopharmaceuticalStartTime &&
- radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== undefined &&
- typeof radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== 'string'
- ) {
- const timeString = convertInterfaceTimeToString(
- radiopharmaceuticalInfo.radiopharmaceuticalStartTime
- )
- instanceMetadata.RadiopharmaceuticalStartTime = timeString
- }
-
- if (startTimeOverride) {
- instanceMetadata.RadiopharmaceuticalStartTime = startTimeOverride
- }
-
- if (
- instanceMetadata.AcquisitionTime &&
- instanceMetadata.AcquisitionTime !== undefined &&
- typeof instanceMetadata.AcquisitionTime !== 'string'
- ) {
- const timeString = convertInterfaceTimeToString(
- instanceMetadata.AcquisitionTime
- )
- instanceMetadata.AcquisitionTime = timeString
- }
-
- if (
- instanceMetadata.SeriesTime &&
- instanceMetadata.SeriesTime !== undefined &&
- typeof instanceMetadata.SeriesTime !== 'string'
- ) {
- const timeString = convertInterfaceTimeToString(
- instanceMetadata.SeriesTime
- )
- instanceMetadata.SeriesTime = timeString
- }
-
- if (
- ptImageModule.frameReferenceTime &&
- ptImageModule.frameReferenceTime !== undefined
- ) {
- instanceMetadata.FrameReferenceTime = ptImageModule.frameReferenceTime
- }
-
- if (
- ptImageModule.actualFrameDuration &&
- ptImageModule.actualFrameDuration !== undefined
- ) {
- instanceMetadata.ActualFrameDuration = ptImageModule.actualFrameDuration
- }
-
- if (
- patientStudyModule.patientSex &&
- patientStudyModule.patientSex !== undefined
- ) {
- instanceMetadata.PatientSex = patientStudyModule.patientSex
- }
- if (ptClinicalData && ptClinicalData.PatientSex != null && ptClinicalData.PatientSex !== '') {
- instanceMetadata.PatientSex = ptClinicalData.PatientSex
- }
-
- if (
- patientStudyModule.patientSize &&
- patientStudyModule.patientSize !== undefined
- ) {
- instanceMetadata.PatientSize = patientStudyModule.patientSize
- }
+ assignIfPresent(
+ instanceMetadata,
+ 'RadiopharmaceuticalStartDateTime',
+ toDicomDateString(radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime)
+ )
+ assignIfPresent(instanceMetadata, 'RadiopharmaceuticalStartTime', startTime)
+ assignIfPresent(instanceMetadata, 'FrameReferenceTime', ptImageModule.frameReferenceTime)
+ assignIfPresent(instanceMetadata, 'ActualFrameDuration', ptImageModule.actualFrameDuration)
+ assignIfPresent(
+ instanceMetadata,
+ 'PatientSex',
+ firstValue(ptClinicalData?.PatientSex, patientStudyModule.patientSex)
+ )
+ assignIfPresent(instanceMetadata, 'PatientSize', patientStudyModule.patientSize)
return instanceMetadata
}
@@ -244,15 +138,13 @@ function convertInterfaceTimeToString(time) {
'0'
)
- const timeString = `${hours}${minutes}${seconds}.${fractionalSeconds}`
- return timeString
+ return `${hours}${minutes}${seconds}.${fractionalSeconds}`
}
function convertInterfaceDateToString(date) {
const month = `${date.month}`.padStart(2, '0')
const day = `${date.day}`.padStart(2, '0')
- const dateString = `${date.year}${month}${day}`
- return dateString
+ return `${date.year}${month}${day}`
}
export { getPTImageIdInstanceMetadata }
@@ -271,7 +163,7 @@ function getInstanceIdFromImageId(imageId) {
}
function normalizeDicomTime(value) {
- if (value === undefined || value === null || value === '') return null
+ if (!hasValue(value)) return null
if (typeof value === 'object') {
return convertInterfaceTimeToString(value)
}
@@ -293,3 +185,34 @@ function normalizeDicomTime(value) {
const frac = `${fracRaw || ''}`.padEnd(6, '0').slice(0, 6)
return `${base}.${frac}`
}
+
+function hasValue(value) {
+ return value !== undefined && value !== null && value !== ''
+}
+
+function firstValue(...values) {
+ return values.find(hasValue)
+}
+
+function toNumber(...values) {
+ const value = firstValue(...values)
+ if (!hasValue(value)) return value
+
+ const numberValue = Number(value)
+ return Number.isFinite(numberValue) ? numberValue : value
+}
+
+function toDicomTimeString(value) {
+ if (!hasValue(value)) return undefined
+ return typeof value === 'string' ? value : convertInterfaceTimeToString(value)
+}
+
+function toDicomDateString(value) {
+ if (!hasValue(value)) return undefined
+ return typeof value === 'string' ? value : convertInterfaceDateToString(value)
+}
+
+function assignIfPresent(target, key, value) {
+ if (!hasValue(value)) return
+ target[key] = value
+}
diff --git a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/ptScalingMetaDataProvider.js b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/ptScalingMetaDataProvider.js
index 33e55fd4..f91066a4 100644
--- a/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/ptScalingMetaDataProvider.js
+++ b/src/views/trials/trials-panel/reading/dicoms/components/Fusion/js/ptScalingMetaDataProvider.js
@@ -1,17 +1,31 @@
-import { utilities as csUtils } from '@cornerstonejs/core'
-
-const scalingPerImageId = {}
-
-function addInstance(imageId, scalingMetaData) {
- const imageURI = csUtils.imageIdToURI(imageId)
- scalingPerImageId[imageURI] = scalingMetaData
-}
-
-function get(type, imageId) {
- if (type === 'scalingModule') {
- const imageURI = csUtils.imageIdToURI(imageId)
- return scalingPerImageId[imageURI]
- }
-}
-
-export default { addInstance, get }
+import { utilities as csUtils } from '@cornerstonejs/core'
+
+const scalingPerImageId = {}
+
+function normalizeImageURI(imageURI) {
+ if (!imageURI) return imageURI
+ const qIndex = imageURI.indexOf('?')
+ if (qIndex === -1) return imageURI
+ const base = imageURI.slice(0, qIndex)
+ const query = imageURI.slice(qIndex + 1)
+ const params = new URLSearchParams(query)
+ if (!params.has('frame')) return imageURI
+ params.delete('frame')
+ const rest = params.toString()
+ return rest ? `${base}?${rest}` : base
+}
+
+function addInstance(imageId, scalingMetaData) {
+ // 统一缩放元数据的 key:忽略 frame 参数,避免 getCurrentImageId() 带 frame 导致查不到 scalingModule
+ const imageURI = normalizeImageURI(csUtils.imageIdToURI(imageId))
+ scalingPerImageId[imageURI] = scalingMetaData
+}
+
+function get(type, imageId) {
+ if (type === 'scalingModule') {
+ const imageURI = normalizeImageURI(csUtils.imageIdToURI(imageId))
+ return scalingPerImageId[imageURI]
+ }
+}
+
+export default { addInstance, get }
diff --git a/src/views/trials/trials-panel/reading/dicoms/components/StudyList.vue b/src/views/trials/trials-panel/reading/dicoms/components/StudyList.vue
index 67e70262..ba349335 100644
--- a/src/views/trials/trials-panel/reading/dicoms/components/StudyList.vue
+++ b/src/views/trials/trials-panel/reading/dicoms/components/StudyList.vue
@@ -99,6 +99,37 @@
@@ -1108,3 +1139,46 @@ export default { background-color: #213a54; } + diff --git a/src/views/trials/trials-panel/reading/dicoms/customize/CustomizeDicomCanvas.vue b/src/views/trials/trials-panel/reading/dicoms/customize/CustomizeDicomCanvas.vue index eea7bdf4..92e832e6 100644 --- a/src/views/trials/trials-panel/reading/dicoms/customize/CustomizeDicomCanvas.vue +++ b/src/views/trials/trials-panel/reading/dicoms/customize/CustomizeDicomCanvas.vue @@ -1081,7 +1081,8 @@ export default { // resolve() // }) this.loading = true - cornerstone.metaData.addProvider(metaDataProvider, 1); + cornerstone.metaData.removeProvider(metaDataProvider) + cornerstone.metaData.addProvider(metaDataProvider, 100000) cornerstone.loadAndCacheImage(this.stack.imageIds[this.stack.currentImageIdIndex]) .then(async image => { if (this.stack.imageIds.indexOf(image.imageId) !== -1) { diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue b/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue index 09adf380..70f042bc 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue @@ -577,7 +577,7 @@ import colorMap from './colorMap.vue' import RectangleROITool from './tools/RectangleROITool' import ScaleOverlayTool from './tools/ScaleOverlayTool' import SegmentBidirectionalTool from './tools/SegmentBidirectionalTool' -import { setPTClinicalDataForInstance } from '@/utils/ptClinicalDataCache' +import { setPTClinicalDataForInstance, clearPTClinicalDataCache } from '@/utils/ptClinicalDataCache' import FixedRadiusCircleROITool from './tools/FixedRadiusCircleROITool' import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom' import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom' @@ -1156,7 +1156,7 @@ export default { let keySeriesIndex = -1 const arr = res1.Result arr.forEach((study, studyIndex) => { - // 缓存接口返回的 PT 临床数据:用于后续统一 SUV 计算(3D suvFactor 计算优先用接口值) + // 仅对 PT/PET study 缓存临床参数,供 3D SUV 计算链路覆盖原始 DICOM 元数据 const ptClinicalData = { PatientSex: study.PatientSex, PatientWeight: study.PatientWeight, @@ -1165,17 +1165,21 @@ export default { RadiopharmaceuticalStartTime: study.RadiopharmaceuticalStartTime, AcquisitionTime: study.AcquisitionTime } + const isPtStudy = ['PT、CT', 'CT、PT', 'PET-CT'].includes(study.Modalities) const hasPtClinicalData = - ptClinicalData.PatientWeight != null || - ptClinicalData.RadionuclideTotalDose != null || - ptClinicalData.RadionuclideHalfLife != null || - ptClinicalData.RadiopharmaceuticalStartTime != null || - ptClinicalData.AcquisitionTime != null + isPtStudy && + ( + ptClinicalData.PatientWeight !== null || + ptClinicalData.RadionuclideTotalDose !== null || + ptClinicalData.RadionuclideHalfLife !== null || + ptClinicalData.RadiopharmaceuticalStartTime !== null || + ptClinicalData.AcquisitionTime !== null + ) study.SeriesList.forEach((series, seriesIndex) => { const imageIds = [] const stack = [] series.InstanceInfoList.forEach((instance, instanceIndex) => { - if (hasPtClinicalData) { + if (hasPtClinicalData && ['PT', 'PET'].includes(String(series.Modality).toUpperCase())) { setPTClinicalDataForInstance(instance.Id, ptClinicalData) } if (study.IsCriticalSequence) { @@ -4423,6 +4427,7 @@ export default { }, }, beforeDestroy() { + clearPTClinicalDataCache() DicomEvent.$off('isCanActiveNoneDicomTool') DicomEvent.$off('removeNoneDicomMeasureData') DicomEvent.$off('addNoneDicomMeasureData') diff --git a/src/views/trials/trials-panel/reading/dicoms3D/components/StudyList.vue b/src/views/trials/trials-panel/reading/dicoms3D/components/StudyList.vue index f58686e8..b5595516 100644 --- a/src/views/trials/trials-panel/reading/dicoms3D/components/StudyList.vue +++ b/src/views/trials/trials-panel/reading/dicoms3D/components/StudyList.vue @@ -76,7 +76,7 @@