* NOTASK: PoC for loading data from Healthcare API * NOTASK: PoC for loading data from Healthcare API fix * Add sample dialog for loading test dicom-picker * Fix incorrect promise resolving * NOTASK: PoC for loading dicom store * Add web-components as git submodules. (Probably will be changed later) * Add rough dicomStore picker * Add dataset selector * NOTASK: QIDO metadata first draft * NOTASK: Not Implemented error for color images * NOTASK: clean-up * Dicom files uploader intermediate version * NOTASK: Accept header * NOTASK: docker configs * NOTASK: fix * NOTASK: howTo * Add draft of DICOM uploader * Add missing files * Fix error with missing clientId * Update submodule * NOTASK: HowTo * NOTASK: config for dev * NOTASK: new docker file * clean-up * Update GCP web-components * Add integration with async web components * Fix errors in nginx.conf * Structured reports views basic implementation * NOTASK: qido -> wado * NOTASK: config fix * NOTASK: dirty copy-paste implementation * Add "Change dicom store" button. Add an ability to clear date. Fix bug, when OAuth dosn't work if URL is not / * Fix studylist filtering * Fix dockerfile * NOTASK: meteor update * NOTASK: fix of package structure * NOTASK: merge fix * improvements Move check for now SR into SR modal Use simple button to open SR modal Remove button styling cleanup * copy SR data retrieval to wado * make search of SR simpler * Codestyle fix * NOTASK: palette color error message * Fix date filter. Fix server settings on first loading. Add SR and PS buttons * NOTASK: PS is now hidden by default * NOTASK: dirty solution * NOTASK: new wadoimage lib version * Fix PS and SR buttons * Make modal dialogs vertically centered * Update web components. Quick UI fixes * Add missing files to the previous commit * NOTASK: show-hide for PS * NOTASK: show-hide for PS * NOTASK: fix of min-max PixelValue bug * Add "sign out" functionality for Google OAuth. Update web-components. * intermediate commit * Add demo signin page * Beautifying demo sign in page * NOTASK: merge * NOTASK: clean-up * Use npm for getting healthcare-api-adapter instead of git submodule. * NOTASK: clean-up * meteor update * clean-up * clean-up * Clean up code * Update healthcare-api-adapter version. Add location to dicom store path. Fix buttons style. * Downgrade meteor and packages to master version. Use meteor-build-client-fixed2
489 lines
18 KiB
JavaScript
489 lines
18 KiB
JavaScript
import { OHIF } from 'meteor/ohif:core';
|
|
import DICOMwebClient from 'dicomweb-client';
|
|
|
|
import { parseFloatArray } from '../../lib/parseFloatArray';
|
|
|
|
const { DICOMWeb } = OHIF;
|
|
|
|
/**
|
|
* Simple cache schema for retrieved color palettes.
|
|
*/
|
|
const paletteColorCache = {
|
|
count: 0,
|
|
maxAge: 24 * 60 * 60 * 1000, // 24h cache?
|
|
entries: {},
|
|
isValidUID: function (paletteUID) {
|
|
return typeof paletteUID === 'string' && paletteUID.length > 0;
|
|
},
|
|
get: function (paletteUID) {
|
|
let entry = null;
|
|
if (this.entries.hasOwnProperty(paletteUID)) {
|
|
entry = this.entries[paletteUID];
|
|
// check how the entry is...
|
|
if ((Date.now() - entry.time) > this.maxAge) {
|
|
// entry is too old... remove entry.
|
|
delete this.entries[paletteUID];
|
|
this.count--;
|
|
entry = null;
|
|
}
|
|
}
|
|
return entry;
|
|
},
|
|
add: function (entry) {
|
|
if (this.isValidUID(entry.uid)) {
|
|
let paletteUID = entry.uid;
|
|
if (this.entries.hasOwnProperty(paletteUID) !== true) {
|
|
this.count++; // increment cache entry count...
|
|
}
|
|
entry.time = Date.now();
|
|
this.entries[paletteUID] = entry;
|
|
// @TODO: Add logic to get rid of old entries and reduce memory usage...
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Returns a WADO url for an instance
|
|
*
|
|
* @param studyInstanceUid
|
|
* @param seriesInstanceUid
|
|
* @param sopInstanceUid
|
|
* @returns {string}
|
|
*/
|
|
function buildInstanceWadoUrl(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid) {
|
|
// TODO: This can be removed, since DICOMWebClient has the same function. Not urgent, though
|
|
const params = [];
|
|
|
|
params.push('requestType=WADO');
|
|
params.push(`studyUID=${studyInstanceUid}`);
|
|
params.push(`seriesUID=${seriesInstanceUid}`);
|
|
params.push(`objectUID=${sopInstanceUid}`);
|
|
params.push('contentType=application/dicom');
|
|
params.push('transferSyntax=*');
|
|
|
|
const paramString = params.join('&');
|
|
|
|
return `${server.wadoUriRoot}?${paramString}`;
|
|
}
|
|
|
|
function buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid) {
|
|
return `${server.wadoRoot}/studies/${studyInstanceUid}/series/${seriesInstanceUid}/instances/${sopInstanceUid}`
|
|
}
|
|
|
|
function buildInstanceFrameWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) {
|
|
const baseWadoRsUri = buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
|
|
frame = frame != null || 1;
|
|
|
|
return `${baseWadoRsUri}/frames/${frame}`
|
|
}
|
|
|
|
/**
|
|
* Parses the SourceImageSequence, if it exists, in order
|
|
* to return a ReferenceSOPInstanceUID. The ReferenceSOPInstanceUID
|
|
* is used to refer to this image in any accompanying DICOM-SR documents.
|
|
*
|
|
* @param instance
|
|
* @returns {String} The ReferenceSOPInstanceUID
|
|
*/
|
|
function getSourceImageInstanceUid(instance) {
|
|
// TODO= Parse the whole Source Image Sequence
|
|
// This is a really poor workaround for now.
|
|
// Later we should probably parse the whole sequence.
|
|
var SourceImageSequence = instance['00082112'];
|
|
if (SourceImageSequence && SourceImageSequence.Value && SourceImageSequence.Value.length) {
|
|
return SourceImageSequence.Value[0]['00081155'].Value[0];
|
|
}
|
|
}
|
|
|
|
function getPaletteColor(server, instance, tag, lutDescriptor) {
|
|
const numLutEntries = lutDescriptor[0];
|
|
const bits = lutDescriptor[2];
|
|
|
|
let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server)
|
|
|
|
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
|
// incorrect bulk data URIs
|
|
if (server.wadoRoot.indexOf('https') === 0 &&
|
|
!uri.includes('https')) {
|
|
uri = uri.replace('http', 'https');
|
|
}
|
|
|
|
const config = {
|
|
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
|
headers: OHIF.DICOMWeb.getAuthorizationHeader()
|
|
};
|
|
const dicomWeb = new DICOMwebClient.api.DICOMwebClient(config);
|
|
const options = {
|
|
BulkDataURI: uri
|
|
};
|
|
|
|
const readUInt16 = (byteArray, position) => {
|
|
return byteArray[position] + (byteArray[position + 1] * 256);
|
|
}
|
|
|
|
const arrayBufferToPaletteColorLUT = (arraybuffer) =>{
|
|
const byteArray = new Uint8Array(arraybuffer);
|
|
const lut = [];
|
|
|
|
for (let i = 0; i < numLutEntries; i++) {
|
|
if (bits === 16) {
|
|
lut[i] = readUInt16(byteArray, i * 2);
|
|
} else {
|
|
lut[i] = byteArray[i];
|
|
}
|
|
}
|
|
|
|
return lut;
|
|
}
|
|
|
|
return dicomWeb.retrieveBulkData(options).then(arrayBufferToPaletteColorLUT)
|
|
}
|
|
|
|
/**
|
|
* Fetch palette colors for instances with "PALETTE COLOR" photometricInterpretation.
|
|
*
|
|
* @param server {Object} Current server;
|
|
* @param instance {Object} The retrieved instance metadata;
|
|
* @returns {String} The ReferenceSOPInstanceUID
|
|
*/
|
|
async function getPaletteColors(server, instance, lutDescriptor) {
|
|
let paletteUID = DICOMWeb.getString(instance['00281199']);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (paletteColorCache.isValidUID(paletteUID)) {
|
|
const entry = paletteColorCache.get(paletteUID);
|
|
|
|
if (entry) {
|
|
return resolve(entry);
|
|
}
|
|
}
|
|
|
|
// no entry in cache... Fetch remote data.
|
|
const r = getPaletteColor(server, instance, '00281201', lutDescriptor);
|
|
const g = getPaletteColor(server, instance, '00281202', lutDescriptor);;
|
|
const b = getPaletteColor(server, instance, '00281203', lutDescriptor);;
|
|
|
|
const promises = [r, g, b];
|
|
|
|
Promise.all(promises).then((args) => {
|
|
entry = {
|
|
red: args[0],
|
|
green: args[1],
|
|
blue: args[2]
|
|
};
|
|
|
|
// when paletteUID is present, the entry can be cached...
|
|
entry.uid = paletteUID;
|
|
paletteColorCache.add(entry);
|
|
|
|
resolve(entry);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getFrameIncrementPointer(element) {
|
|
const frameIncrementPointerNames = {
|
|
'00181065': 'frameTimeVector',
|
|
'00181063': 'frameTime'
|
|
};
|
|
|
|
if(!element || !element.Value || !element.Value.length) {
|
|
return;
|
|
}
|
|
|
|
const value = element.Value[0];
|
|
return frameIncrementPointerNames[value];
|
|
}
|
|
|
|
function getRadiopharmaceuticalInfo(instance) {
|
|
const modality = DICOMWeb.getString(instance['00080060']);
|
|
|
|
if (modality !== 'PT') {
|
|
return;
|
|
}
|
|
|
|
const radiopharmaceuticalInfo = instance['00540016'];
|
|
if ((radiopharmaceuticalInfo === undefined) || !radiopharmaceuticalInfo.Value || !radiopharmaceuticalInfo.Value.length) {
|
|
return;
|
|
}
|
|
|
|
const firstPetRadiopharmaceuticalInfo = radiopharmaceuticalInfo.Value[0];
|
|
return {
|
|
radiopharmaceuticalStartTime: DICOMWeb.getString(firstPetRadiopharmaceuticalInfo['00181072']),
|
|
radionuclideTotalDose: DICOMWeb.getNumber(firstPetRadiopharmaceuticalInfo['00181074']),
|
|
radionuclideHalfLife: DICOMWeb.getNumber(firstPetRadiopharmaceuticalInfo['00181075'])
|
|
};
|
|
}
|
|
|
|
function getRelationshipString (data) {
|
|
const relationshipType = DICOMWeb.getString(data['0040A010']);
|
|
|
|
switch (relationshipType) {
|
|
case 'HAS CONCEPT MOD':
|
|
return 'Concept modifier: ';
|
|
case 'HAS OBS CONTEXT':
|
|
return 'Observation context: ';
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
|
|
const getNestedObject = (data) => data.Value[0] || {};
|
|
|
|
const getMeaningString = (data) => (data['0040A043'] && `${DICOMWeb.getString(data['0040A043'].Value[0]['00080104'])} = `) || '';
|
|
|
|
function getValueString (data) {
|
|
switch (DICOMWeb.getString(data['0040A040'])) { // ValueType
|
|
case 'CODE':
|
|
const conceptCode = getNestedObject(data['0040A168']);
|
|
const conceptCodeValue = DICOMWeb.getString(conceptCode['00080100']);
|
|
const conceptCodeMeaning = DICOMWeb.getString(conceptCode['00080104']);
|
|
const schemeDesignator = DICOMWeb.getString(conceptCode['00080102']);
|
|
return `${conceptCodeMeaning} (${conceptCodeValue}, ${schemeDesignator})`;
|
|
|
|
case 'PNAME':
|
|
return DICOMWeb.getName(data['0040A123']);
|
|
|
|
case 'TEXT':
|
|
return DICOMWeb.getString(data['0040A160']);
|
|
|
|
case 'UIDREF':
|
|
return DICOMWeb.getString(data['0040A124']);
|
|
|
|
case 'NUM':
|
|
const numValue = DICOMWeb.getString(getNestedObject(data['0040A300'])['0040A30A']);
|
|
const codeValue = DICOMWeb.getString(getNestedObject(getNestedObject(data['0040A300'])['004008EA'])['00080100']);
|
|
return `${numValue} ${codeValue}`;
|
|
}
|
|
}
|
|
|
|
function constructPlainValue(data) {
|
|
const value = getValueString(data);
|
|
|
|
if (value) {
|
|
return getRelationshipString(data) + getMeaningString(data) + value;
|
|
}
|
|
}
|
|
|
|
function constructContentSequence(data, header) {
|
|
if (!data['0040A730'].Value) {
|
|
return;
|
|
}
|
|
|
|
const items = data['0040A730'].Value.map(item => parseContent(item)).filter(item => item);
|
|
|
|
if (items.length) {
|
|
const result = {
|
|
items
|
|
};
|
|
|
|
if (header) {
|
|
result.header = header;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively parses content sequence for structured reports
|
|
*
|
|
* @param instance The instance
|
|
* @returns {Array} Series List
|
|
*/
|
|
|
|
function parseContent(instance) {
|
|
if (instance['0040A040']) { // ValueType
|
|
if (DICOMWeb.getString(instance['0040A040']) === 'CONTAINER') {
|
|
const header = DICOMWeb.getString(getNestedObject(instance['0040A043'])['00080104']); // TODO: check with real data
|
|
return constructContentSequence(instance, header);
|
|
}
|
|
|
|
return constructPlainValue(instance);
|
|
}
|
|
|
|
if (instance['0040A730']) { //ContentSequence
|
|
return constructContentSequence(instance);
|
|
}
|
|
}
|
|
|
|
function getModality(instance) {
|
|
const modality = DICOMWeb.getString(instance['00080060']);
|
|
return modality || (!!instance['0040A730'] && 'SR') || undefined; // FIXME: dirty, dirty hack, we use
|
|
}
|
|
|
|
function getContentDateTime(instance) {
|
|
const date = DICOMWeb.getString(instance['00080023']);
|
|
const time = DICOMWeb.getString(instance['00080033']);
|
|
|
|
if (date && time) {
|
|
return `${date.substr(0, 4)}-${date.substr(4, 2)}-${date.substr(6, 2)} ${time.substr(0, 2)}:${time.substr(2, 2)}:${time.substr(4, 2)}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses result data from a WADO search into Study MetaData
|
|
* Returns an object populated with study metadata, including the
|
|
* series list.
|
|
*
|
|
* @param server
|
|
* @param studyInstanceUid
|
|
* @param resultData
|
|
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
|
|
*/
|
|
async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
|
if (!resultData.length) {
|
|
return;
|
|
}
|
|
|
|
const anInstance = resultData[0];
|
|
if (!anInstance) {
|
|
return;
|
|
}
|
|
|
|
const studyData = {
|
|
seriesList: [],
|
|
studyInstanceUid,
|
|
wadoUriRoot: server.wadoUriRoot,
|
|
patientName: DICOMWeb.getName(anInstance['00100010']),
|
|
patientId: DICOMWeb.getString(anInstance['00100020']),
|
|
patientAge: DICOMWeb.getNumber(anInstance['00101010']),
|
|
patientSize: DICOMWeb.getNumber(anInstance['00101020']),
|
|
patientWeight: DICOMWeb.getNumber(anInstance['00101030']),
|
|
accessionNumber: DICOMWeb.getString(anInstance['00080050']),
|
|
studyDate: DICOMWeb.getString(anInstance['00080020']),
|
|
modalities: DICOMWeb.getString(anInstance['00080061']),
|
|
studyDescription: DICOMWeb.getString(anInstance['00081030']),
|
|
imageCount: DICOMWeb.getString(anInstance['00201208']),
|
|
studyInstanceUid: DICOMWeb.getString(anInstance['0020000D']),
|
|
institutionName: DICOMWeb.getString(anInstance['00080080'])
|
|
};
|
|
|
|
const seriesMap = {};
|
|
|
|
await Promise.all(resultData.map(async function(instance) {
|
|
const seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
|
|
let series = seriesMap[seriesInstanceUid];
|
|
const modality = getModality(instance);
|
|
|
|
if (!series) {
|
|
series = {
|
|
seriesDescription: DICOMWeb.getString(instance['0008103E']),
|
|
modality,
|
|
seriesInstanceUid: seriesInstanceUid,
|
|
seriesNumber: DICOMWeb.getNumber(instance['00200011']),
|
|
seriesDate: DICOMWeb.getString(instance['00080021']),
|
|
seriesTime: DICOMWeb.getString(instance['00080031']),
|
|
instances: []
|
|
};
|
|
seriesMap[seriesInstanceUid] = series;
|
|
studyData.seriesList.push(series);
|
|
}
|
|
|
|
const sopInstanceUid = DICOMWeb.getString(instance['00080018']);
|
|
const wadouri = buildInstanceWadoUrl(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
|
|
const baseWadoRsUri = buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
|
|
const wadorsuri = buildInstanceFrameWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
|
|
const instanceSummary = {
|
|
contentSequence: parseContent(instance),
|
|
completionFlag: DICOMWeb.getString(instance['0040A491']),
|
|
manufacturer: DICOMWeb.getString(instance['00080070']),
|
|
verificationFlag: DICOMWeb.getString(instance['0040A493']),
|
|
contentDateTime: getContentDateTime(instance),
|
|
imageType: DICOMWeb.getString(instance['00080008']),
|
|
sopClassUid: DICOMWeb.getString(instance['00080016']),
|
|
modality,
|
|
sopInstanceUid,
|
|
instanceNumber: DICOMWeb.getNumber(instance['00200013']),
|
|
imagePositionPatient: DICOMWeb.getString(instance['00200032']),
|
|
imageOrientationPatient: DICOMWeb.getString(instance['00200037']),
|
|
frameOfReferenceUID: DICOMWeb.getString(instance['00200052']),
|
|
sliceLocation: DICOMWeb.getNumber(instance['00201041']),
|
|
samplesPerPixel: DICOMWeb.getNumber(instance['00280002']),
|
|
photometricInterpretation: DICOMWeb.getString(instance['00280004']),
|
|
planarConfiguration: DICOMWeb.getNumber(instance['00280006']),
|
|
rows: DICOMWeb.getNumber(instance['00280010']),
|
|
columns: DICOMWeb.getNumber(instance['00280011']),
|
|
pixelSpacing: DICOMWeb.getString(instance['00280030']),
|
|
pixelAspectRatio: DICOMWeb.getString(instance['00280034']),
|
|
bitsAllocated: DICOMWeb.getNumber(instance['00280100']),
|
|
bitsStored: DICOMWeb.getNumber(instance['00280101']),
|
|
highBit: DICOMWeb.getNumber(instance['00280102']),
|
|
pixelRepresentation: DICOMWeb.getNumber(instance['00280103']),
|
|
smallestPixelValue: DICOMWeb.getNumber(instance['00280106']),
|
|
largestPixelValue: DICOMWeb.getNumber(instance['00280107']),
|
|
windowCenter: DICOMWeb.getString(instance['00281050']),
|
|
windowWidth: DICOMWeb.getString(instance['00281051']),
|
|
rescaleIntercept: DICOMWeb.getNumber(instance['00281052']),
|
|
rescaleSlope: DICOMWeb.getNumber(instance['00281053']),
|
|
rescaleType: DICOMWeb.getNumber(instance['00281054']),
|
|
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
|
|
laterality: DICOMWeb.getString(instance['00200062']),
|
|
viewPosition: DICOMWeb.getString(instance['00185101']),
|
|
acquisitionDateTime: DICOMWeb.getString(instance['0008002A']),
|
|
numberOfFrames: DICOMWeb.getNumber(instance['00280008']),
|
|
frameIncrementPointer: getFrameIncrementPointer(instance['00280009']),
|
|
frameTime: DICOMWeb.getNumber(instance['00181063']),
|
|
frameTimeVector: parseFloatArray(DICOMWeb.getString(instance['00181065'])),
|
|
sliceThickness: DICOMWeb.getNumber(instance['00180050']),
|
|
lossyImageCompression: DICOMWeb.getString(instance['00282110']),
|
|
derivationDescription: DICOMWeb.getString(instance['00282111']),
|
|
lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']),
|
|
lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']),
|
|
echoNumber: DICOMWeb.getString(instance['00180086']),
|
|
contrastBolusAgent: DICOMWeb.getString(instance['00180010']),
|
|
radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance),
|
|
baseWadoRsUri: baseWadoRsUri,
|
|
wadouri: wadouri,
|
|
wadorsuri: wadorsuri,
|
|
imageRendering: server.imageRendering,
|
|
thumbnailRendering: server.thumbnailRendering
|
|
};
|
|
|
|
// Get additional information if the instance uses "PALETTE COLOR" photometric interpretation
|
|
if (instanceSummary.photometricInterpretation === 'PALETTE COLOR') {
|
|
const redPaletteColorLookupTableDescriptor = parseFloatArray(DICOMWeb.getString(instance['00281101']));
|
|
const greenPaletteColorLookupTableDescriptor = parseFloatArray(DICOMWeb.getString(instance['00281102']));
|
|
const bluePaletteColorLookupTableDescriptor = parseFloatArray(DICOMWeb.getString(instance['00281103']));
|
|
const palettes = await getPaletteColors(server, instance, redPaletteColorLookupTableDescriptor);
|
|
|
|
if (palettes) {
|
|
if (palettes.uid) {
|
|
instanceSummary.paletteColorLookupTableUID = palettes.uid;
|
|
}
|
|
|
|
instanceSummary.redPaletteColorLookupTableData = palettes.red;
|
|
instanceSummary.greenPaletteColorLookupTableData = palettes.green;
|
|
instanceSummary.bluePaletteColorLookupTableData = palettes.blue;
|
|
instanceSummary.redPaletteColorLookupTableDescriptor = redPaletteColorLookupTableDescriptor;
|
|
instanceSummary.greenPaletteColorLookupTableDescriptor = greenPaletteColorLookupTableDescriptor;
|
|
instanceSummary.bluePaletteColorLookupTableDescriptor = bluePaletteColorLookupTableDescriptor;
|
|
}
|
|
}
|
|
|
|
series.instances.push(instanceSummary);
|
|
}));
|
|
return studyData;
|
|
}
|
|
|
|
/**
|
|
* Retrieve Study MetaData from a DICOM server using a WADO call
|
|
*
|
|
* @param server
|
|
* @param studyInstanceUid
|
|
* @returns {Promise}
|
|
*/
|
|
OHIF.studies.services.WADO.RetrieveMetadata = async function(server, studyInstanceUid) {
|
|
const config = {
|
|
url: server.wadoRoot,
|
|
headers: OHIF.DICOMWeb.getAuthorizationHeader()
|
|
};
|
|
const dicomWeb = new DICOMwebClient.api.DICOMwebClient(config);
|
|
const options = {
|
|
studyInstanceUID: studyInstanceUid
|
|
};
|
|
|
|
return dicomWeb.retrieveStudyMetadata(options).then(result => {
|
|
return resultDataToStudyMetadata(server, studyInstanceUid, result);
|
|
});
|
|
};
|