feat(dicomJSON): Add Loading Other Display Sets and JSON Metadata Generation script (#3777)
This commit is contained in:
parent
5b513fd30e
commit
43b1c17209
265
.scripts/dicom-json-generator.js
Normal file
265
.scripts/dicom-json-generator.js
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
* This script uses nodejs to generate a JSON file from a DICOM study folder.
|
||||||
|
* You need to have dcmjs installed in your project.
|
||||||
|
* The JSON file can be used to load the study into the OHIF Viewer. You can get more detail
|
||||||
|
* in the DICOM JSON Data source on docs.ohif.org
|
||||||
|
*
|
||||||
|
* Usage: node dicomStudyToJSONLaunch.js <studyFolder> <urlPrefix> <outputJSONPath>
|
||||||
|
*
|
||||||
|
* params:
|
||||||
|
* - studyFolder: path to the study folder
|
||||||
|
* - urlPrefix: prefix to the url that will be used to load the study into the viewer. For instance
|
||||||
|
* we use https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data as the urlPrefix for the
|
||||||
|
* example since the data is hosted on S3 and each study is in a folder. So the url in the generated
|
||||||
|
* json file for the first instance of the first series of the first study will be
|
||||||
|
* dicomweb:https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data/Series1/Instance1
|
||||||
|
* - outputJSONPath: path to the output JSON file
|
||||||
|
*/
|
||||||
|
const dcmjs = require('dcmjs');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const [studyDirectory, urlPrefix, outputPath] = args;
|
||||||
|
|
||||||
|
if (args.length !== 3) {
|
||||||
|
console.error('Usage: node dicomStudyToJSONLaunch.js <studyFolder> <urlPrefix> <outputJSONPath>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = {
|
||||||
|
studies: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath) {
|
||||||
|
try {
|
||||||
|
const files = await recursiveReadDir(studyDirectory);
|
||||||
|
console.debug('Processing...');
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (!file.includes('.DS_Store') && !file.includes('.xml')) {
|
||||||
|
const arrayBuffer = await fs.readFile(file);
|
||||||
|
const dicomDict = dcmjs.data.DicomMessage.readFile(arrayBuffer.buffer);
|
||||||
|
const instance = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomDict.dict);
|
||||||
|
|
||||||
|
instance.fileLocation = createImageId(file, urlPrefix, studyDirectory);
|
||||||
|
processInstance(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Successfully loaded data');
|
||||||
|
|
||||||
|
model.studies.forEach(study => {
|
||||||
|
study.NumInstances = findInstancesNumber(study);
|
||||||
|
study.Modalities = findModalities(study).join('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.writeFile(outputPath, JSON.stringify(model, null, 2));
|
||||||
|
console.log('JSON saved');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function recursiveReadDir(dir) {
|
||||||
|
let results = [];
|
||||||
|
const list = await fs.readdir(dir);
|
||||||
|
for (const file of list) {
|
||||||
|
const filePath = path.resolve(dir, file);
|
||||||
|
const stat = await fs.stat(filePath);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const res = await recursiveReadDir(filePath);
|
||||||
|
results = results.concat(res);
|
||||||
|
} else {
|
||||||
|
results.push(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createImageId(fileLocation, urlPrefix, studyDirectory) {
|
||||||
|
const relativePath = path.relative(studyDirectory, fileLocation);
|
||||||
|
const normalizedPath = path.normalize(relativePath).replace(/\\/g, '/');
|
||||||
|
return `dicomweb:${urlPrefix}${normalizedPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processInstance(instance) {
|
||||||
|
const { StudyInstanceUID, SeriesInstanceUID } = instance;
|
||||||
|
let study = getStudy(StudyInstanceUID);
|
||||||
|
|
||||||
|
if (!study) {
|
||||||
|
study = createStudyMetadata(StudyInstanceUID, instance);
|
||||||
|
model.studies.push(study);
|
||||||
|
}
|
||||||
|
|
||||||
|
let series = getSeries(StudyInstanceUID, SeriesInstanceUID);
|
||||||
|
|
||||||
|
if (!series) {
|
||||||
|
series = createSeriesMetadata(instance);
|
||||||
|
study.series.push(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceMetaData =
|
||||||
|
instance.NumberOfFrames > 1
|
||||||
|
? createInstanceMetaDataMultiFrame(instance)
|
||||||
|
: createInstanceMetaData(instance);
|
||||||
|
|
||||||
|
series.instances.push(...[].concat(instanceMetaData));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStudy(StudyInstanceUID) {
|
||||||
|
return model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSeries(StudyInstanceUID, SeriesInstanceUID) {
|
||||||
|
const study = getStudy(StudyInstanceUID);
|
||||||
|
return study
|
||||||
|
? study.series.find(series => series.SeriesInstanceUID === SeriesInstanceUID)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findInstancesNumber = study => {
|
||||||
|
let numInstances = 0;
|
||||||
|
study.series.forEach(aSeries => {
|
||||||
|
numInstances = numInstances + aSeries.instances.length;
|
||||||
|
});
|
||||||
|
return numInstances;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findModalities = study => {
|
||||||
|
let modalities = new Set();
|
||||||
|
study.series.forEach(aSeries => {
|
||||||
|
modalities.add(aSeries.Modality);
|
||||||
|
});
|
||||||
|
return Array.from(modalities);
|
||||||
|
};
|
||||||
|
|
||||||
|
function createStudyMetadata(StudyInstanceUID, instance) {
|
||||||
|
return {
|
||||||
|
StudyInstanceUID,
|
||||||
|
StudyDescription: instance.StudyDescription,
|
||||||
|
StudyDate: instance.StudyDate,
|
||||||
|
StudyTime: instance.StudyTime,
|
||||||
|
PatientName: instance.PatientName,
|
||||||
|
PatientID: instance.PatientID || '1234', // this is critical to have
|
||||||
|
AccessionNumber: instance.AccessionNumber,
|
||||||
|
PatientAge: instance.PatientAge,
|
||||||
|
PatientSex: instance.PatientSex,
|
||||||
|
PatientWeight: instance.PatientWeight,
|
||||||
|
series: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function createSeriesMetadata(instance) {
|
||||||
|
return {
|
||||||
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
|
SeriesDescription: instance.SeriesDescription,
|
||||||
|
SeriesNumber: instance.SeriesNumber,
|
||||||
|
SeriesTime: instance.SeriesTime,
|
||||||
|
Modality: instance.Modality,
|
||||||
|
SliceThickness: instance.SliceThickness,
|
||||||
|
instances: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function commonMetaData(instance) {
|
||||||
|
return {
|
||||||
|
Columns: instance.Columns,
|
||||||
|
Rows: instance.Rows,
|
||||||
|
InstanceNumber: instance.InstanceNumber,
|
||||||
|
SOPClassUID: instance.SOPClassUID,
|
||||||
|
AcquisitionNumber: instance.AcquisitionNumber,
|
||||||
|
PhotometricInterpretation: instance.PhotometricInterpretation,
|
||||||
|
BitsAllocated: instance.BitsAllocated,
|
||||||
|
BitsStored: instance.BitsStored,
|
||||||
|
PixelRepresentation: instance.PixelRepresentation,
|
||||||
|
SamplesPerPixel: instance.SamplesPerPixel,
|
||||||
|
PixelSpacing: instance.PixelSpacing,
|
||||||
|
HighBit: instance.HighBit,
|
||||||
|
ImageOrientationPatient: instance.ImageOrientationPatient,
|
||||||
|
ImagePositionPatient: instance.ImagePositionPatient,
|
||||||
|
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||||
|
ImageType: instance.ImageType,
|
||||||
|
Modality: instance.Modality,
|
||||||
|
SOPInstanceUID: instance.SOPInstanceUID,
|
||||||
|
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||||
|
StudyInstanceUID: instance.StudyInstanceUID,
|
||||||
|
WindowCenter: instance.WindowCenter,
|
||||||
|
WindowWidth: instance.WindowWidth,
|
||||||
|
RescaleIntercept: instance.RescaleIntercept,
|
||||||
|
RescaleSlope: instance.RescaleSlope,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function conditionalMetaData(instance) {
|
||||||
|
return {
|
||||||
|
...(instance.ConceptNameCodeSequence && {
|
||||||
|
ConceptNameCodeSequence: instance.ConceptNameCodeSequence,
|
||||||
|
}),
|
||||||
|
...(instance.SeriesDate && { SeriesDate: instance.SeriesDate }),
|
||||||
|
...(instance.ReferencedSeriesSequence && {
|
||||||
|
ReferencedSeriesSequence: instance.ReferencedSeriesSequence,
|
||||||
|
}),
|
||||||
|
...(instance.SharedFunctionalGroupsSequence && {
|
||||||
|
SharedFunctionalGroupsSequence: instance.SharedFunctionalGroupsSequence,
|
||||||
|
}),
|
||||||
|
...(instance.PerFrameFunctionalGroupsSequence && {
|
||||||
|
PerFrameFunctionalGroupsSequence: instance.PerFrameFunctionalGroupsSequence,
|
||||||
|
}),
|
||||||
|
...(instance.ContentSequence && { ContentSequence: instance.ContentSequence }),
|
||||||
|
...(instance.ContentTemplateSequence && {
|
||||||
|
ContentTemplateSequence: instance.ContentTemplateSequence,
|
||||||
|
}),
|
||||||
|
...(instance.CurrentRequestedProcedureEvidenceSequence && {
|
||||||
|
CurrentRequestedProcedureEvidenceSequence: instance.CurrentRequestedProcedureEvidenceSequence,
|
||||||
|
}),
|
||||||
|
...(instance.CodingSchemeIdentificationSequence && {
|
||||||
|
CodingSchemeIdentificationSequence: instance.CodingSchemeIdentificationSequence,
|
||||||
|
}),
|
||||||
|
...(instance.RadiopharmaceuticalInformationSequence && {
|
||||||
|
RadiopharmaceuticalInformationSequence: instance.RadiopharmaceuticalInformationSequence,
|
||||||
|
}),
|
||||||
|
...(instance.ROIContourSequence && {
|
||||||
|
ROIContourSequence: instance.ROIContourSequence,
|
||||||
|
}),
|
||||||
|
...(instance.StructureSetROISequence && {
|
||||||
|
StructureSetROISequence: instance.StructureSetROISequence,
|
||||||
|
}),
|
||||||
|
...(instance.ReferencedFrameOfReferenceSequence && {
|
||||||
|
ReferencedFrameOfReferenceSequence: instance.ReferencedFrameOfReferenceSequence,
|
||||||
|
}),
|
||||||
|
...(instance.CorrectedImage && { CorrectedImage: instance.CorrectedImage }),
|
||||||
|
...(instance.Units && { Units: instance.Units }),
|
||||||
|
...(instance.DecayCorrection && { DecayCorrection: instance.DecayCorrection }),
|
||||||
|
...(instance.AcquisitionDate && { AcquisitionDate: instance.AcquisitionDate }),
|
||||||
|
...(instance.AcquisitionTime && { AcquisitionTime: instance.AcquisitionTime }),
|
||||||
|
...(instance.PatientWeight && { PatientWeight: instance.PatientWeight }),
|
||||||
|
...(instance.NumberOfFrames && { NumberOfFrames: instance.NumberOfFrames }),
|
||||||
|
...(instance.FrameTime && { FrameTime: instance.FrameTime }),
|
||||||
|
...(instance.EncapsulatedDocument && { EncapsulatedDocument: instance.EncapsulatedDocument }),
|
||||||
|
...(instance.SequenceOfUltrasoundRegions && {
|
||||||
|
SequenceOfUltrasoundRegions: instance.SequenceOfUltrasoundRegions,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInstanceMetaData(instance) {
|
||||||
|
const metadata = {
|
||||||
|
...commonMetaData(instance),
|
||||||
|
...conditionalMetaData(instance),
|
||||||
|
};
|
||||||
|
return { metadata, url: instance.fileLocation };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInstanceMetaDataMultiFrame(instance) {
|
||||||
|
const instances = [];
|
||||||
|
const commonData = commonMetaData(instance);
|
||||||
|
const conditionalData = conditionalMetaData(instance);
|
||||||
|
|
||||||
|
for (let i = 1; i <= instance.NumberOfFrames; i++) {
|
||||||
|
const metadata = { ...commonData, ...conditionalData };
|
||||||
|
const result = { metadata, url: instance.fileLocation + `?frame=${i}` };
|
||||||
|
instances.push(result);
|
||||||
|
}
|
||||||
|
return instances;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertDICOMToJSON(studyDirectory, urlPrefix, outputPath);
|
||||||
@ -200,7 +200,11 @@ function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sopClassUids, images } = newDisplaySet;
|
if (newDisplaySet.unsupported) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { sopClassUids } = newDisplaySet;
|
||||||
|
|
||||||
// Check if any have the newDisplaySet is the correct SOPClass.
|
// Check if any have the newDisplaySet is the correct SOPClass.
|
||||||
unloadedMeasurements = unloadedMeasurements.filter(measurement =>
|
unloadedMeasurements = unloadedMeasurements.filter(measurement =>
|
||||||
|
|||||||
@ -176,6 +176,7 @@ class DicomLoaderService {
|
|||||||
authorizationHeaders,
|
authorizationHeaders,
|
||||||
wadoRoot,
|
wadoRoot,
|
||||||
wadoUri,
|
wadoUri,
|
||||||
|
instance,
|
||||||
} = dataset;
|
} = dataset;
|
||||||
// Retrieve wadors or just try to fetch wadouri
|
// Retrieve wadors or just try to fetch wadouri
|
||||||
if (!someInvalidStrings(wadoRoot)) {
|
if (!someInvalidStrings(wadoRoot)) {
|
||||||
@ -188,6 +189,13 @@ class DicomLoaderService {
|
|||||||
);
|
);
|
||||||
} else if (!someInvalidStrings(wadoUri)) {
|
} else if (!someInvalidStrings(wadoUri)) {
|
||||||
return fetchIt(wadoUri, { headers: authorizationHeaders });
|
return fetchIt(wadoUri, { headers: authorizationHeaders });
|
||||||
|
} else if (!someInvalidStrings(instance?.url)) {
|
||||||
|
// make sure the url is absolute, remove the scope
|
||||||
|
// from it if it is not absolute. For instance it might be dicomweb:http://....
|
||||||
|
// and we need to remove the dicomweb: part
|
||||||
|
const url = instance.url;
|
||||||
|
const absoluteUrl = url.startsWith('http') ? url : url.substring(url.indexOf(':') + 1);
|
||||||
|
return fetchIt(absoluteUrl, { headers: authorizationHeaders });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,23 @@ let _store = {
|
|||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function wrapSequences(obj) {
|
||||||
|
return Object.keys(obj).reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||||
|
// Recursively wrap sequences for nested objects
|
||||||
|
acc[key] = wrapSequences(obj[key]);
|
||||||
|
} else {
|
||||||
|
acc[key] = obj[key];
|
||||||
|
}
|
||||||
|
if (key.endsWith('Sequence')) {
|
||||||
|
acc[key] = OHIF.utils.addAccessors(acc[key]);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
Array.isArray(obj) ? [] : {}
|
||||||
|
);
|
||||||
|
}
|
||||||
const getMetaDataByURL = url => {
|
const getMetaDataByURL = url => {
|
||||||
return _store.urls.find(metaData => metaData.url === url);
|
return _store.urls.find(metaData => metaData.url === url);
|
||||||
};
|
};
|
||||||
@ -190,8 +207,14 @@ function createDicomJSONApi(dicomJsonConfig) {
|
|||||||
const numberOfSeries = series.length;
|
const numberOfSeries = series.length;
|
||||||
series.forEach((series, index) => {
|
series.forEach((series, index) => {
|
||||||
const instances = series.instances.map(instance => {
|
const instances = series.instances.map(instance => {
|
||||||
|
// for instance.metadata if the key ends with sequence then
|
||||||
|
// we need to add a proxy to the first item in the sequence
|
||||||
|
// so that we can access the value of the sequence
|
||||||
|
// by using sequenceName.value
|
||||||
|
const modifiedMetadata = wrapSequences(instance.metadata);
|
||||||
|
|
||||||
const obj = {
|
const obj = {
|
||||||
...instance.metadata,
|
...modifiedMetadata,
|
||||||
url: instance.url,
|
url: instance.url,
|
||||||
imageId: instance.url,
|
imageId: instance.url,
|
||||||
...series,
|
...series,
|
||||||
|
|||||||
66
platform/core/src/utils/addAccessors.js
Normal file
66
platform/core/src/utils/addAccessors.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
const handler = {
|
||||||
|
/**
|
||||||
|
* Get a proxied value from the array or property value
|
||||||
|
* Note that the property value get works even if you update the underlying object.
|
||||||
|
* Also, return true of proxy.__isProxy in order to distinguish proxies and not double proxy them.
|
||||||
|
*/
|
||||||
|
get: (target, prop) => {
|
||||||
|
if (prop == '__isProxy') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (prop in target) {
|
||||||
|
return target[prop];
|
||||||
|
}
|
||||||
|
return target[0][prop];
|
||||||
|
},
|
||||||
|
|
||||||
|
set: (obj, prop, value) => {
|
||||||
|
if (typeof prop === 'number' || prop in obj) {
|
||||||
|
obj[prop] = value;
|
||||||
|
} else {
|
||||||
|
obj[0][prop] = value;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a proxy object for sqZero or the src[0] element if sqZero is unspecified, AND
|
||||||
|
* src is an array of length 1.
|
||||||
|
*
|
||||||
|
* If sqZero isn't passed in, then assume this is a create call on the destination object
|
||||||
|
* itself, by:
|
||||||
|
* 1. If not an object, return dest
|
||||||
|
* 2. If an array of length != 1, return dest
|
||||||
|
* 3. If an array, use dest[0] as sqZero
|
||||||
|
* 4. Use dest as sqZero
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* src = [{a:5,b:'string', c:null}]
|
||||||
|
* addAccessors(src)
|
||||||
|
* src.c = 'outerChange'
|
||||||
|
* src[0].b='innerChange'
|
||||||
|
*
|
||||||
|
* assert src.a===5
|
||||||
|
* assert src[0].c === 'outerChange'
|
||||||
|
* assert src.b === 'innerChange'
|
||||||
|
*/
|
||||||
|
const addAccessors = (dest, sqZero) => {
|
||||||
|
if (dest.__isProxy) {
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
let itemZero = sqZero;
|
||||||
|
if (itemZero === undefined) {
|
||||||
|
if (typeof dest !== 'object') {
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
if (Array.isArray(dest) && dest.length !== 1) {
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
itemZero = Array.isArray(dest) ? dest[0] : dest;
|
||||||
|
}
|
||||||
|
const ret = [itemZero];
|
||||||
|
return new Proxy(ret, handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAccessors;
|
||||||
@ -27,6 +27,7 @@ import debounce from './debounce';
|
|||||||
import roundNumber from './roundNumber';
|
import roundNumber from './roundNumber';
|
||||||
import downloadCSVReport from './downloadCSVReport';
|
import downloadCSVReport from './downloadCSVReport';
|
||||||
import isEqualWithin from './isEqualWithin';
|
import isEqualWithin from './isEqualWithin';
|
||||||
|
import addAccessors from './addAccessors';
|
||||||
import {
|
import {
|
||||||
sortStudy,
|
sortStudy,
|
||||||
sortStudySeries,
|
sortStudySeries,
|
||||||
@ -65,6 +66,7 @@ const utils = {
|
|||||||
Queue,
|
Queue,
|
||||||
isDicomUid,
|
isDicomUid,
|
||||||
isEqualWithin,
|
isEqualWithin,
|
||||||
|
addAccessors,
|
||||||
resolveObjectPath,
|
resolveObjectPath,
|
||||||
hierarchicalListUtils,
|
hierarchicalListUtils,
|
||||||
progressTrackingUtils,
|
progressTrackingUtils,
|
||||||
|
|||||||
@ -40,6 +40,7 @@ describe('Top level exports', () => {
|
|||||||
'progressTrackingUtils',
|
'progressTrackingUtils',
|
||||||
'subscribeToNextViewportGridChange',
|
'subscribeToNextViewportGridChange',
|
||||||
'uuidv4',
|
'uuidv4',
|
||||||
|
'addAccessors',
|
||||||
].sort();
|
].sort();
|
||||||
|
|
||||||
const exports = Object.keys(utils.default).sort();
|
const exports = Object.keys(utils.default).sort();
|
||||||
|
|||||||
@ -28,6 +28,20 @@ dataset. Let's have a look at the JSON file:
|
|||||||
JSON file stores the metadata for the study level, series level and instance
|
JSON file stores the metadata for the study level, series level and instance
|
||||||
level. A JSON launch file should follow the same structure as the one below.
|
level. A JSON launch file should follow the same structure as the one below.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
You can use our script to generate the JSON file from a hosted endpoint. See
|
||||||
|
`.scripts/dicom-json-generator.js`
|
||||||
|
|
||||||
|
You could run it like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node .scripts/dicom-json-generator.js '/path/to/study/folder' 'url/to/dicom/server/folder' 'json/output/file.json'
|
||||||
|
```
|
||||||
|
|
||||||
|
Some modalities require additional metadata to be added to the JSON file. You can read more about the minimum amount of metadata required for the viewer to work [here](../../faq.md#what-are-the-list-of-required-metadata-for-the-ohif-viewer-to-work). We will handle this in the script. For example, the script will add the CodeSequences for SR in order to display the measurements in the viewer.
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
Note that at the instance level metadata we are storing both the `metadata` and
|
Note that at the instance level metadata we are storing both the `metadata` and
|
||||||
also the `url` for the dicom file on the dicom server. In this case we are
|
also the `url` for the dicom file on the dicom server. In this case we are
|
||||||
referring to
|
referring to
|
||||||
@ -35,6 +49,8 @@ referring to
|
|||||||
which is stored in another directory in our s3. (You can actually try
|
which is stored in another directory in our s3. (You can actually try
|
||||||
downloading the dicom file by opening the url in your browser).
|
downloading the dicom file by opening the url in your browser).
|
||||||
|
|
||||||
|
The URL to the script in the given example is `https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178`. This URL serves as the parent directory that contains all the series within their respective folders.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"studies": [
|
"studies": [
|
||||||
|
|||||||
@ -62,7 +62,9 @@ WorkList by adding the `dataSources` query parameter.
|
|||||||
/?dataSources=orthanc
|
/?dataSources=orthanc
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: you should pass the `sourceName` of the data source in the configuration file (not the friendly name nor the name)
|
Note1: You should pass the `sourceName` of the data source in the configuration file (not the friendly name nor the name)
|
||||||
|
Note2: Make sure that the configuration file you are using actually includes that data source. You cannot use a data source from another configuration file.
|
||||||
|
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user