feat(customizationService): Enable saving and loading of private tags in SRs (#3842)
This commit is contained in:
parent
d1c07d90f0
commit
e1f55e65f2
@ -40,7 +40,9 @@ const _generateReport = (measurementData, additionalFindingTypes, options = {})
|
||||
return dataset;
|
||||
};
|
||||
|
||||
const commandsModule = ({}) => {
|
||||
const commandsModule = props => {
|
||||
const { servicesManager } = props;
|
||||
const { customizationService } = servicesManager.services;
|
||||
const actions = {
|
||||
/**
|
||||
*
|
||||
@ -95,7 +97,15 @@ const commandsModule = ({}) => {
|
||||
throw new Error('Invalid report, no content');
|
||||
}
|
||||
|
||||
await dataSource.store.dicom(naturalizedReport);
|
||||
const onBeforeDicomStore =
|
||||
customizationService.getModeCustomization('onBeforeDicomStore')?.value;
|
||||
|
||||
let dicomDict;
|
||||
if (typeof onBeforeDicomStore === 'function') {
|
||||
dicomDict = onBeforeDicomStore({ measurementData, naturalizedReport });
|
||||
}
|
||||
|
||||
await dataSource.store.dicom(naturalizedReport, null, dicomDict);
|
||||
|
||||
if (StudyInstanceUID) {
|
||||
dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
|
||||
|
||||
@ -171,7 +171,12 @@ function _load(displaySet, servicesManager, extensionManager) {
|
||||
|
||||
// Check currently added displaySets and add measurements if the sources exist.
|
||||
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(displaySet, activeDisplaySet, dataSource);
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
activeDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
);
|
||||
});
|
||||
|
||||
// Subscribe to new displaySets as the source may come in after.
|
||||
@ -180,12 +185,23 @@ function _load(displaySet, servicesManager, extensionManager) {
|
||||
// If there are still some measurements that have not yet been loaded into cornerstone,
|
||||
// See if we can load them onto any of the new displaySets.
|
||||
displaySetsAdded.forEach(newDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(displaySet, newDisplaySet, dataSource);
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
newDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource) {
|
||||
function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
srDisplaySet,
|
||||
newDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
) {
|
||||
const { customizationService } = servicesManager.services;
|
||||
let unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||
measurement => measurement.loaded === false
|
||||
);
|
||||
@ -244,7 +260,20 @@ function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dat
|
||||
|
||||
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||
const measurement = unloadedMeasurements[j];
|
||||
let measurement = unloadedMeasurements[j];
|
||||
|
||||
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
|
||||
'onBeforeSRAddMeasurement'
|
||||
)?.value;
|
||||
|
||||
if (typeof onBeforeSRAddMeasurement === 'function') {
|
||||
measurement = onBeforeSRAddMeasurement({
|
||||
measurement,
|
||||
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
||||
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
|
||||
});
|
||||
}
|
||||
|
||||
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
||||
addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID);
|
||||
|
||||
|
||||
@ -339,7 +339,9 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
const textLines = this._getTextBoxLinesFromLabels(label);
|
||||
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
||||
|
||||
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
||||
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
||||
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
||||
}
|
||||
|
||||
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@ export default function addMeasurement(measurement, imageId, displaySetInstanceU
|
||||
data: {
|
||||
label: measurement.labels,
|
||||
handles: {
|
||||
textBox: {},
|
||||
textBox: measurement.textBox ?? {},
|
||||
},
|
||||
cachedStats: {
|
||||
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
||||
|
||||
@ -90,7 +90,7 @@ export default function hydrateStructuredReport(
|
||||
const datasetToUse = _mapLegacyDataSet(instance);
|
||||
|
||||
// Use dcmjs to generate toolState.
|
||||
const storedMeasurementByAnnotationType = MeasurementReport.generateToolState(
|
||||
let storedMeasurementByAnnotationType = MeasurementReport.generateToolState(
|
||||
datasetToUse,
|
||||
// NOTE: we need to pass in the imageIds to dcmjs since the we use them
|
||||
// for the imageToWorld transformation. The following assumes that the order
|
||||
@ -101,6 +101,16 @@ export default function hydrateStructuredReport(
|
||||
metaData
|
||||
);
|
||||
|
||||
const onBeforeSRHydration =
|
||||
customizationService.getModeCustomization('onBeforeSRHydration')?.value;
|
||||
|
||||
if (typeof onBeforeSRHydration === 'function') {
|
||||
storedMeasurementByAnnotationType = onBeforeSRHydration({
|
||||
storedMeasurementByAnnotationType,
|
||||
displaySet,
|
||||
});
|
||||
}
|
||||
|
||||
// Filter what is found by DICOM SR to measurements we support.
|
||||
const mappingDefinitions = mappings.map(m => m.annotationType);
|
||||
const hydratableMeasurementsInSR = {};
|
||||
|
||||
@ -50,7 +50,7 @@ const Angle = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@ -62,6 +62,7 @@ const Angle = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -48,7 +48,7 @@ const Length = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@ -59,6 +59,7 @@ const Length = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -45,7 +45,7 @@ const Bidirectional = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@ -57,6 +57,7 @@ const Bidirectional = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -43,7 +43,7 @@ const CircleROI = {
|
||||
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService);
|
||||
|
||||
@ -55,6 +55,7 @@ const CircleROI = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -50,7 +50,7 @@ const CobbAngle = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@ -62,6 +62,7 @@ const CobbAngle = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -43,7 +43,7 @@ const EllipticalROI = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@ -55,6 +55,7 @@ const EllipticalROI = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -53,7 +53,7 @@ const Length = {
|
||||
displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, displaySetService);
|
||||
|
||||
@ -65,6 +65,7 @@ const Length = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -48,7 +48,7 @@ const PlanarFreehandROI = {
|
||||
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService);
|
||||
|
||||
@ -60,6 +60,7 @@ const PlanarFreehandROI = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -43,7 +43,7 @@ const RectangleROI = {
|
||||
displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
|
||||
}
|
||||
|
||||
const { points } = data.handles;
|
||||
const { points, textBox } = data.handles;
|
||||
|
||||
const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService);
|
||||
|
||||
@ -55,6 +55,7 @@ const RectangleROI = {
|
||||
SOPInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
points,
|
||||
textBox,
|
||||
metadata,
|
||||
referenceSeriesUID: SeriesInstanceUID,
|
||||
referenceStudyUID: StudyInstanceUID,
|
||||
|
||||
@ -40,7 +40,8 @@ const metadataProvider = classes.MetadataProvider;
|
||||
* @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
|
||||
* @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true
|
||||
*/
|
||||
function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
function createDicomWebApi(dicomWebConfig, servicesManager) {
|
||||
const { userAuthenticationService, customizationService } = servicesManager.services;
|
||||
let dicomWebConfigCopy,
|
||||
qidoConfig,
|
||||
wadoConfig,
|
||||
@ -140,7 +141,13 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
instances: {
|
||||
search: (studyInstanceUid, queryParameters) => {
|
||||
qidoDicomWebClient.headers = getAuthrorizationHeader();
|
||||
return qidoSearch.call(undefined, qidoDicomWebClient, studyInstanceUid, null, queryParameters);
|
||||
return qidoSearch.call(
|
||||
undefined,
|
||||
qidoDicomWebClient,
|
||||
studyInstanceUid,
|
||||
null,
|
||||
queryParameters
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -211,7 +218,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
},
|
||||
|
||||
store: {
|
||||
dicom: async (dataset, request) => {
|
||||
dicom: async (dataset, request, dicomDict) => {
|
||||
wadoDicomWebClient.headers = getAuthrorizationHeader();
|
||||
if (dataset instanceof ArrayBuffer) {
|
||||
const options = {
|
||||
@ -220,21 +227,26 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
||||
};
|
||||
await wadoDicomWebClient.storeInstances(options);
|
||||
} else {
|
||||
const meta = {
|
||||
FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value,
|
||||
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
||||
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
||||
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
|
||||
ImplementationClassUID,
|
||||
ImplementationVersionName,
|
||||
};
|
||||
let effectiveDicomDict = dicomDict;
|
||||
|
||||
const denaturalized = denaturalizeDataset(meta);
|
||||
const dicomDict = new DicomDict(denaturalized);
|
||||
if (!dicomDict) {
|
||||
const meta = {
|
||||
FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value,
|
||||
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
||||
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
||||
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
|
||||
ImplementationClassUID,
|
||||
ImplementationVersionName,
|
||||
};
|
||||
|
||||
dicomDict.dict = denaturalizeDataset(dataset);
|
||||
const denaturalized = denaturalizeDataset(meta);
|
||||
const defaultDicomDict = new DicomDict(denaturalized);
|
||||
defaultDicomDict.dict = denaturalizeDataset(dataset);
|
||||
|
||||
const part10Buffer = dicomDict.write();
|
||||
effectiveDicomDict = defaultDicomDict;
|
||||
}
|
||||
|
||||
const part10Buffer = effectiveDicomDict.write();
|
||||
|
||||
const options = {
|
||||
datasets: [part10Buffer],
|
||||
|
||||
@ -9,7 +9,7 @@ import { createDicomWebApi } from '../DicomWebDataSource/index';
|
||||
* dicomWeb configuration array
|
||||
*
|
||||
*/
|
||||
function createDicomWebProxyApi(dicomWebProxyConfig, UserAuthenticationService) {
|
||||
function createDicomWebProxyApi(dicomWebProxyConfig, servicesManager) {
|
||||
const { name } = dicomWebProxyConfig;
|
||||
let dicomWebDelegate = undefined;
|
||||
|
||||
@ -28,7 +28,7 @@ function createDicomWebProxyApi(dicomWebProxyConfig, UserAuthenticationService)
|
||||
|
||||
dicomWebDelegate = createDicomWebApi(
|
||||
data.servers.dicomWeb[0].configuration,
|
||||
UserAuthenticationService
|
||||
servicesManager
|
||||
);
|
||||
dicomWebDelegate.initialize({ params, query });
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ export const callByRetrieveAETitle = ({
|
||||
|
||||
function createMergeDataSourceApi(
|
||||
mergeConfig: MergeConfig,
|
||||
UserAuthenticationService: unknown,
|
||||
servicesManager: unknown,
|
||||
extensionManager
|
||||
) {
|
||||
const { seriesMerge } = mergeConfig;
|
||||
|
||||
@ -461,10 +461,9 @@ export default class ExtensionManager extends PubSubService {
|
||||
|
||||
this.dataSourceDefs[dataSourceDef.sourceName] = dataSourceDef;
|
||||
|
||||
const { userAuthenticationService } = this._servicesManager.services;
|
||||
const dataSourceInstance = module.createDataSource(
|
||||
dataSourceDef.configuration,
|
||||
userAuthenticationService,
|
||||
this._servicesManager,
|
||||
this
|
||||
);
|
||||
|
||||
|
||||
@ -58,6 +58,7 @@ const MEASUREMENT_SCHEMA_KEYS = [
|
||||
'longestDiameter',
|
||||
'cachedStats',
|
||||
'selected',
|
||||
'textBox',
|
||||
];
|
||||
|
||||
const EVENTS = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user